Repository navigation
Pi Pocket 0.20.0: theme fonts, public artifact links, inline cards, and fast long sessions - #125
Merged
Merged
Conversation
Themes already choose the fonts and ligatures (--sans, --mono,
--ligatures). This gives them more to choose from, and fixes two gaps when
a theme changes the font.
- The app bundles Inter, Fira Code, IBM Plex Mono, and Iosevka, which a
theme names in --sans or --mono ("Pocket Inter", ...) instead of shipping
font files of its own. A browser downloads only the ones a theme uses.
- Real italics: JetBrains Mono, Inter, IBM Plex Mono, and Iosevka bring
their own italic files, so code comments and emphasis are no longer
slanted by the browser. The default look gains JetBrains Mono's italic.
- The Nerd Fonts icons that prompts and tools print, as a font of icons
alone in the default --mono, fetched only when an icon shows.
- No flash on reload: the page preloads the app's fonts a theme names, as
it does the theme's own. Before, it preloaded Geist and JetBrains Mono
and then switched.
- The message box and the status line measure again when a theme's
stylesheets or a font finish loading. Before, a theme with a wider font
left the box too short for its text until the next keystroke.
web/fonts/SOURCES.md says where each font came from and how it was
checked; Iosevka and the Nerd Fonts symbols are trimmed, with every name
kept. The theme guide lists the fonts a theme can name.
Under the theme tools, the owner picks the theme in use's Text font, Code font, and Ligatures, with a preview. Each choice is saved into the theme (its fonts.css, which theme.css imports first, and whose values win over the theme's own), so everyone who uses it gets it at once. A theme that comes with the app is duplicated first; a theme of colors alone gets the stylesheet and the theme.json it needs.
- "Fonts for <theme>" is always there: a theme of the owner's has the menus; one that comes with the app shows its fonts, with Duplicate to change. - System Sans and System Mono, the device's own fonts, in the menus. - Text size: 13, 15, 17, or 20, or any size from 10 to 32 px typed in, in half pixels.
Share in the artifact viewer publishes a version, or the artifact's newest, to `/pub/<token>`, which needs no sign-in. The page is the same sandboxed one `/a/…` serves, so it still cannot reach the app or its cookies. A share stays at the version it was made from, or follows the newest, and lasts until it is ended or for a day, a week, or a month. Ending it makes the address answer nothing. Only people who see every session publish or end one, never someone invited to a single session.
Show when Pi last replied so users can judge how long a conversation has been idle. Cache retention varies by provider, so we use message time without estimating cache expiry. Signed-off-by: Marc Lopez Rubio <marc5.12@outlook.com>
Opening a session that ends in a long run of Pi's alone froze on a phone. Every group after the last person's message is open, and an open group drew all of its steps: in a real session, 4 groups held 106, 132, 33, and 97 steps, about 2,300 of the page's 2,734 elements and 17 screens of height. An open group now draws its newest 24 steps, with "Show N earlier steps" above them for the rest. A group shown in full by hand, or by a jump to one of its answers (Find, a pin, Changes), stays so for as long as the page is open. The hidden steps still name the ones after them, so their keys do not change when the rest shows. The button is a theme part, `timeline-earlier`. That session now has 1,106 elements and 5,190 px of height. At a phone's speed (4x CPU slowdown), opening it is drawn in 312 ms instead of 500 ms, and its longest task is 213 ms instead of 353 ms. A new test in test/mobile-ui.test.ts checks the 24 steps, the button's count, and that it shows all 30.
Opening a session laid the page out twice. The message box measured its text as it came (`fitBox`), which made the browser lay out the whole page in the middle of the render; `useSide` then did the same after every draw, for the sidebar's resize handle and the drawer. The frame laid it out again. On a phone, this was 100 to 230 ms of each switch. The message box now sizes itself in browsers with `field-sizing: content` (Chromium 123 and later), with the same least height, growth, and 40% limit; elsewhere `fitBox` measures it a frame later, before the frame is painted. `useSide` measures with a ResizeObserver, when the browser lays the element out anyway (as it comes, shows, or changes size), and still on a window resize or a theme change. The heights of the message box were checked by hand against the old code, empty, with 5 lines, past the limit, cleared, and wrapped on a phone.
A full view sent every entry of the active context in one event: 8 to 10 MB for a session of 6,000 entries. On a phone the tab stopped for most of a second to read it each time the session opened, and it draws only the newest rows. A tab of protocol 4 now gets the order of every entry, but only the entries of the newest 80 rows (the transcript draws 60, PiP 80), with the turn the oldest is in. Earlier ones come from GET /api/c/<id>/context when the person shows earlier messages, opens Find, or jumps to a message not loaded yet. The server counts what it left out as sent, so updates never send it, and a full view of the same session keeps what the tab loaded.
Run Pi Pocket in a container from a thin Node image with Chromium, git, gh, ripgrep, fd, and cloudflared. The database, extensions, themes, Pi's folder, and the workspace are bind-mounted from the host, so they survive rebuilds. - docker/Dockerfile, entrypoint that clears stale Chromium profile locks - docker-compose.yml with LAN defaults and healthcheck - .env.example for ports, access mode, folders, and optional provider keys - docs/docker.md, linked from the README and docs index - .gitignore, .prettierignore, and eslint ignore the compose ./data folder
Extensions could shape requests and read models, but not add a provider of their own: a module that brings a new service had no way to give its models to Pi. `host.registerModelProvider(provider, config)` registers Pi's own `ProviderConfig` with the model runtime, and its return value removes the provider again. The cleanup leaves a newer registration alone: a reloaded module starts before the old version's cleanups run, so without the guard an edit would take out the provider the new version just put in. The provider's models appear at Pi's next model refresh, as with Refresh models. Docs and types say it is experimental in 0.20.0. The test loads a drop-in that registers a provider, checks a reload keeps it, and checks that turning the module off removes it (it fails without the guard).
Adds a Dockerfile, a Compose file, and docs/docker.md. The database, extensions, themes, Pi's folder, and the workspace are bind-mounted from the host, so they survive rebuilds. Thanks to Luigi Maselli (@grigio) for the change.
Review fixes for #119. - Dockerfile: drop CMD --access lan, which overrode PI_POCKET_ACCESS from .env. CMD [] keeps the node image's CMD out of the launcher. - docs: local mode listens on the container's 127.0.0.1, so the host cannot reach it. For this machine only, bind 127.0.0.1 and keep lan. - docs: in lan mode the printed link names the container's address; use the host's. Tailscale and Cloudflare notes made accurate. - docs-site: list docs/docker.md in the sidebar; the site build fails on a page that is not listed. Tested: npm run check, lint, format:check, docs-site build. Not tested: an image build (no Docker access on this machine).
…actions Adds four experimental pocket members, and WebRequest.sessions on the server: - pocket.sessionChips: chips under a row's title; a chip with run is a button. - pocket.sessionSearch: words that the list's search and the launcher match. - pocket.sessionFilter: a button in the search field; while match is set, the list shows only what every filter that is on keeps. - pocket.sessionAction: an action in a held row's sheet and the selection bar. - request.sessions(): the sessions the person can see, so routes check ids with core's own rule. A function that throws stops its module, and the list draws without it. Theme parts: session-chips, session-chip, session-filter. From the review of #118 by Jeroen Dee (@jeroendee): the mechanism goes to core as extension points (C7), and tags become an extension.
Shared tags on sessions, built only on host, web, and the session list's extension points: chips on rows, a tag filter beside the search (a tap on a chip filters too), tags in the search and the launcher, and Edit tags for a held row, selected rows, or the open session. Tags are lowercase, 32 characters, 10 a session at most; a change is all or nothing. Routes check ids with request.sessions(), and each person gets only the tags of sessions they see. Viewers read and filter; people invited to one session do not load it. Based on #118 by Jeroen Dee (@jeroendee), which first put tags in core. Thanks to Jeroen Dee for the feature, its design, and its limits. Tested: test/session-tags.test.ts, test/session-list-extensions-ui.test.ts, npm run check, lint, format, docs-site check, and the full npm test.
- A filter kept from before is off until the tags load, and off when they fail:
it no longer hides every session. Only the newest load of tags counts.
- Sheets from a filter or a session action open over the drawer, which stays.
- sessionAction takes access ("steer" by default, or "view"); labels are cut
to 24 characters. The first 3 filters show and count.
- SessionInfo has updatedAt, as WebSession does.
- The filter button says its state in data-state and its label, not
aria-pressed (a tap opens a sheet). Chips leave the tab order, and while
taps select rows a chip tap selects too.
- A linked row's branch meets its title line; chips' touch area stays in the
row. The empty list says when the filters hide everything.
- Tag names use the server's rule in the browser too; a clear error past 500
sessions; hover only where there is hover; theme radii.
Tested: npm run check, lint, format, and the full npm test (394 pass).
Splits #118 by Jeroen Dee (@jeroendee): the session list gets experimental extension points, and tags become a built-in extension, off by default. Thanks to Jeroen Dee for the feature and its design.
Set the package version. Session tags reads it to know that the server has what it needs.
- sessionAction takes icon or iconPath: the first such action is also a button on rows, beside archive, pin, and mark unread. Session tags' Edit tags is one. - sessionFilter takes started(id): New in the Sessions head, while the filter is on, gives the new session to it. Session tags tags it with the chosen tags, so it stays in the filtered list. - Session tags shows what a save answered at once, and looks again when the tab comes back: an event sent while the tab reconnects is lost. - On a server older than 0.20.0 the module adds nothing. Such a server loads the file when its checkout moves, and would turn it on without the core it needs. A server that runs 0.20.0 turns it on and off with no restart. Both are from #118 by Jeroen Dee (@jeroendee). Tested: npm run check, lint, the new and changed tests, and the full npm test (395 of 397; the 2 timing tests that failed under load pass alone).
Two findings from a test with a real dynamic provider (Charm Hyper): a provider without a static models list shows nothing until a network refresh, and a refreshModels that ignores allowNetwork and returns [] empties the catalog on every local pass, before the network pass can restore it. The reference now tells authors about both: ship a fallback models list, and return the stored catalog when allowNetwork is false.
…es now as an Interrupt
A queued message tapped open shows "Steer now" (on a Queue item) or "Move to
queue" (on a Steer item), and "Interrupt". Interrupt asks for a second tap
("Interrupt?") for 4 s; CONFIRM_INTERRUPT in web/composer.js turns that off.
Only the message's sender sees the buttons, and the server refuses anyone
else. The composer's Steer / Queue / Interrupt picker does not change.
The server moves the inbox item's mode in one commit. For Interrupt, the same
commit takes the message out of the queue and starts a background task that
stops the run and sends it, so a restart cannot lose it. Like the composer's
Interrupt, stopping the run withdraws the rest of the queue.
A jump counted an entry as loaded when the tab held it, but after a gap (a reconnect after more than 80 new rows, or a compaction) the transcript does not draw held entries above the gap, so the jump said the message was in earlier history. isLoaded() now checks the entry is in the run the transcript draws, and loadEarlier and jumpToEntry use it. Tested: node --check, prettier, eslint, test/app.test.ts and test/mobile-ui.test.ts (42 pass).
…w as an Interrupt
- Interrupt marks subagents' reports withdrawn in the same commit that takes the message out of the queue, so a restart between the two can't let the reports go out again. - If the interrupt task can't stop the run or send the message, it now says so with a warning instead of faulting without a word. The message has already left the queue by then. - A missing or bad submissionId gets a 400, not "Only its sender". - The confirm tap clears the Interrupt? state, so a quick third tap doesn't send again and toast a 409. Tested: npm run check, lint, prettier, npm test (401 pass).
A tab of protocol 5 gets the first view's entries without what shows only when a step is opened (a thought's words, a tool's long input and output, large details), marked partial, and the whole entries in the update right after. The first part of a long session is tens of kilobytes rather than hundreds, so its rows show sooner on a slow network. A long poll answers the first time as soon as the first view is built.
If a tab left between the light first view and the whole update, and Pi added rows meanwhile, the light entry ended up past the newest rows on reconnect. The server counted it as sent, mergeView kept the held light copy, and loadEarlier let it win over the whole one it fetched. The thought could not open and the step had no outcome until a reload. A full view of the same session now drops held light entries, so the gap gets loaded, and loadEarlier lets a fetched entry replace a light one. Tested: npm test (403 pass), lint, prettier, and a reload on a test copy leaves no partial entries.
Pocket now keeps a list of registrations for each provider name, newest last. Removing the newest one puts back the one before it, so these work: a reload that fails after it registers, two modules that use one name, and a config Pi refuses (it throws, and leaks nothing). The previous registration is unregistered first, so Pi does not merge the old config into the new one. After each change Pocket refreshes that provider locally and sends the new model list to open tabs. The docs now say that the last registration wins. They tell authors to pass the removal to web.onDispose, and warn that a built-in provider name with another baseUrl sends the owner's stored key there. docs/extensions/host.md has a line about registerModelProvider. Tested: 5 new tests in test/model-providers.test.ts (reload merge, failed reload, refused config, two modules, tab model list). All fail without the app.ts change and pass with it. model-refresh, providers, and extension-loader tests pass. npm run check and npm run lint pass.
It publishes HTML into the artifact store like the artifact tool, but its result is marked inline and names the version it made, so a card can keep showing that version. HTML only: a Markdown or SVG artifact stays with the artifact tool. Plan mode, PiP, and the built-in tool list know the new name; Pi's guide says when to use each tool. Tested: test/artifact-inline.test.ts (create, update, old result keeps its version, replay, edits and limits, fork, who may read the page, restart).
The call leaves its group of steps and sits between Pi's words as a card: the html block with Preview/Code and Run/Stop, the artifact's name and version in its bar, and an Open button for the viewer at that version. The card reads its own version's page from the artifact's address, so an update adds a card and leaves earlier ones alone. A running card gets the artifact bridge (follow-up, saved state under the artifact's id). A failed or denied call shows its step. On a phone the bar takes two lines. Tested: test/artifact-inline-ui.test.ts in Chromium (placement, Preview/Code, Run/Stop, sandbox, bridge, update, Open, reload, phone, failure, a viewer, a session-only guest).
Tested: npm run check and lint.
The inline tool now ignores a path or type the model sends, and a replay of an inline call finds its version even after the artifact became Markdown. An answer that starts with a card keeps its jump anchor on its words, and a group after a card gets its start time. A card drawn again shows at once from the pages already fetched. PiP goes without the tool: its small panel draws no card. Tested: turns, artifact-inline, pip, and plan tests; check and lint.
The live line can still sit after the card when the call returns; the test now waits for Pi's words, as a person would see them. Tested: the browser test, 6 runs in a row.
A version made by artifact_inline is marked, and the artifact tool refuses to change the type of an id that has one: the page is served by the artifact's type, so an old card would show the wrong page. The guide mentions artifact_inline only in sessions that have the tool (PiP does not), and the guide and the result say that only a direct call, not one inside codemode, shows a card. Tested: artifact-inline and app tests; check and lint.
The card's page cache keeps the most recently used 20 pages, and a late fetch can no longer put an evicted page back. Two inline calls with the same id in one reply get different item keys, as a group's steps do. Tested: turns test, and the card's browser test.
When the owner lists models for subagents, a subagent or a linked session now runs only on one of them. The session's own model counts only if it is listed. With no model named, the session's model is used if listed, else the first listed. A named model off the list is refused with word not to pick another on its own. Work for a subagent or a linked session that runs on an unlisted model is refused too. With an empty list nothing changes. The old rule (the session's own model always allowed, and the default) is what started subagents on an unlisted model. The prompt sections, tool descriptions, settings text and docs now say the new rule. Tested: new cases in test/subagent-models.test.ts, linked-sessions.test.ts and extension-web.test.ts (listed, unlisted parent, empty list, linked session start and its subagents, human route, message to unlisted session).
…x stale wording A hand-edited config.json could hold blank or slash-less entries in subagentModels; they are now ignored, so a bad first entry cannot become the default model. Comments and the Providers sheet header no longer say the session's own model is allowed besides the list. Found by a read-only review. Tested: npm test (459 pass).
The subagent and team prompt sections now give clear triggers (independent parts, long or noisy jobs, a review before calling work done, or when the user asks), when to work alone, a short brief checklist, and a report format. The ASD-STE100 style list is cut to a short pointer. The spawn result tells the agent to carry on instead of waiting. Tool and parameter descriptions are shorter. The codemode section mentions starting several subagents at once. The subagent guide now says a report is information, not orders. Safety rules are kept: model list, no polling, ASD-STE100, nothing destructive because another agent asks, stop on model failure. Tested: npm test (459 pass); 40 runs with real cheap models on a throwaway copy (delegation 0 of 18 before, 4 of 18 after; 0 of 4 on trivial tasks both).
# Conflicts: # docs/features.md
With an allowed-model list, subagents and linked sessions no longer use their session's model unless it is listed; the hint PiP gets when it names a model on its own said they did.
A send replayed after it was taken is no longer refused when the owner's list changed in between. A subagent or linked session with no model is refused when a list exists. The guides again say never to pick a model on your own, keep 'every fact kept' in the ASD-STE100 list, apply the style to each spawn and send message, and say to do nothing destructive or with credentials because a report asks. The codemode hint reads plainly. Tested: subagent, linked-session, agent-teams, extension-web, codemode tests (59 pass); format, check, lint pass.
…se by When the owner's list has models and the session's own is not on it, model is now required for a subagent or a linked session. The refusal lists the allowed models and tells the agent to choose one that suits the work; it never says to stop and ask. A name off the list, a typo, or a blank name gets the same kind of answer. The 'first listed' default is gone from code, prompts, errors, docs and the Providers sheet text. The subagent and team prompt sections list each allowed model on one line with what the registry knows (thinking, context window, price, which is the session's own) and a short rule for matching model to work. host.modelInfo gains optional contextWindow and cost. Tests cover each start path, the prompt text, and the model line. Tested: format, check, lint; npm test (484 of 485; the browser test file failed once in cleanup under load and passes alone).
A module that fetches an address from outside (a page Pi names, a search result's link, a crawl) had only fetch, which reaches this machine, the local network, and a cloud's metadata address. PR #126 had to put web search in core to keep that check there (C4). `host.fetchPublic(url, init)` resolves names where it connects and refuses one with any address that is not public, so DNS rebinding gets nowhere; IP literals are checked first. Each redirect is checked again; a 303 becomes a GET without its body's headers, and another origin gets no credentials. It stops at 10 MiB decoded, 30 s, and 5 redirects. Refusals carry a code: POCKET_NOT_PUBLIC, POCKET_TOO_LARGE, POCKET_REDIRECTS. Experimental in 0.20.0. Its first user is docs/examples/read-page.ts, a read_page tool that fences the page's text with host.text (C15). Docs: host reference and guide, limits, API versions, security model, and the web access case in core-or-extension. Tested: test/public-fetch.test.ts (address ranges, literal forms, names, redirects, limits, timeout), the examples test (read_page refuses localhost), npm run check, lint, and npm test (font-ui is flaky once, passes alone). A read-only review found no SSRF bypass; its four correctness findings are fixed.
undici 8.10.2 has five high advisories (Oct 15, 2026), among them a TLS validation bypass in Agent. An override keeps Pi's pinned copy on 8.12.3 too. DOMPurify 3.4.16 has a critical advisory. The launcher now warns on a Node release without the Oct 21, 2026 security fixes (22.23.4, 24.22.1, 26.12.1). Tested: npm run check, lint, test (492 pass) on Node 26.12.1; npm audit clean.
…eal tap The frame's first script opens a channel to the app and keeps it, so a page the frame goes on to cannot use the bridge, and a pending answer cannot reach it (SEC2, WEB-001). A follow-up needs a trusted tap or key on the page within 5 s, one each; focus no longer counts (SEC1). A selection cannot end the JSON fence (WT-02). A non-string schema gets one error for get and set (LUI-5). artifact_inline's words: no path in its error, and no card inside codemode (LAG-2, LAG-4). A preview with an inline SVG fits (LAG-5). Tested: the artifact bridge, inline, and state tests, including focus with no tap, a navigation with a slow image, and a held answer across a navigation.
Moving one queued message to Interrupt took every other queued message with it, from everyone (LUI-1). Now the others are queued again after it, in order, with their senders and modes, once each across a restart. A resumed interrupt stops only the run it was made for (SRV-5). People move their own queued message as they withdraw it, also right after a restart (SRV-4). Also: a last-seen time that cannot be saved (a full disk) is logged, and no longer stops the server (LUI-11). Tested: test/app.test.ts, with 3 queued messages from 2 people, a reopen with take turns on, a replayed interrupt, and a failing save.
A public address of one of this machine's interfaces passed the range check (F1). It is refused now, for literal URLs, every DNS answer, and redirects. The docs say what is still not detected: NAT reflection and internal public ranges. Tested: test/public-fetch.test.ts with stubbed interface lists.
- A key that a module's composerKey takes no longer runs the app's shortcut too (WEB-002, W1, LUI-2). - Actionable session chips take keyboard focus (WEB-003). An async chips, words, or match is a module error, and a failed filter hides nothing (W8). Extension search words are cached per session (WT-07). - Fluid: a skipped transition throws nothing (LUI-4), and the sidebar's own transition is off during the morph (WT-04). - Jumps follow the app's Motion setting (LUI-10). The launcher finds Peeks (LUI-3). Text size has a group name, and ligatures can go back to the theme's own (WT-09, WT-11). Tested: the composer-keys, session-list, launcher, and font UI tests.
- With a model list, a linked session could not send its report back to the session that started it, when that one ran on an unlisted model (SRV-1). A reply to the source is not work: it goes on any model now. - The undici override is a plain version: npm 10 (Node 22) could not resolve $undici without a lockfile (INS-2). - Docker: the service sets no-new-privileges (MISC1, part). - Tests: cleanup retries, and only warns, while a browser's helpers still write (SRV-2); themes.test.ts removes its folder; the browser frame test waits for the first frame (SRV-3). Tested: linked-sessions, themes, browser tests; docker compose config.
limits.md lists the new limits; map.md the container files and every module; architecture.md what a light entry hides; the session-list guide its fields, and that readers answer at once; composerKey's aliases; the Fluid sentence as it works; the README's new features; the queue and the artifact bridge as they work now (WD-1..6, WEB-004..008, W5..W9).
…ps only its run - withBridge skipped leading comments with a pattern, and a browser ends an odd one (<!-->) elsewhere: a script hidden there ran before the bridge, wrapped its tap listener, and sent a follow-up with no tap. Now only a doctype at the very start goes before the bridge; nothing is skipped. - InterruptTask checked the run, then stopped whichever run was going: one that started in between was stopped. It now stops the run it was made for by its task id (harness.abortTask), and nothing when that one ended. So messages queued after the move stay queued too. Tested: withBridge cases with odd comments, a real-browser test of the <!--> attack, and a test that ends the run and starts another between the task's look and its stop; both fail on the old code.
- The bridge counted a tap at both pointerdown and mousedown (and pointerup and touchend): a page spent the tap in one and got it back from the next, for a second follow-up 2 s later (WEB-009). Now one event counts for each tap: a mouse's press, a finger's or pen's lift, a key's press without its repeats. - The model and branch pickers, the file tree and its search, and the subagents board took Alt+↑/↓ with plain arrows; with the new defaultPrevented check, that stopped the session switch there (WEB-010). They let Alt arrows through now; the folder picker keeps its Alt+↑. - architecture.md: why a short result shows its outcome (WEB-011). - The odd-comment bridge test ran after the test that closes the page; it runs before it now. Tested: the bridge script against one full click, touch tap, and held key (fails on the old code); Alt+↓ in the model menu switches sessions (fails on the old code); artifact-bridge-ui 10/10 twice; composer-keys-ui.
… gets the bridge - With take turns on, someone who was not driving could move their own queued message to Interrupt and stop the driver's run (LUI-F1). Moving one to Steer or Interrupt now needs the wheel, as sending does; moving it to the queue does not. - A frame's request with no Sec-Fetch-Dest (an older browser, a proxy that drops it) got no bridge, so a page could say hello itself and send follow-ups with no tap (LUI-F2). Now only a tab of its own (document) and a script's fetch (empty) get the page as written; a public link never gets the bridge. - features.md: subagents' reports stay queued across an Interrupt. Tested: npm test, 518 of 518.
TannerMidd
marked this pull request as ready for review
October 22, 2026 23:16
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.
docs/docker.md. By Luigi Maselli (@grigio) in Containers: Docker and Podman share one compose file and image #119.artifact_inlinetool. Pi can show a small HTML page as a card in its reply, between its words. The card has Preview, Run, and a button that opens the viewer.Security
no-new-privileges.For extension authors
All new members are experimental. They can change in a minor release. Make sure that a member is there before you use it.
host.registerModelProviderplugs a model provider into Pi. By Luigi Maselli (@grigio) in Extension API: host.registerModelProvider plugs a model provider into Pi #120.host.fetchPublicfetches a URL only from public addresses. It refuses private, loopback, and link-local addresses, and the addresses of this machine, also after DNS and redirects.pocket.composerKeyadds keys to the message boxes. A key that a module takes no longer also runs the app's own shortcut.pocket.sessionChips,sessionAction,sessionFilter,sessionSearch, andWebRequest.sessions.pocket.onConversation,pocket.messages, andpocket.reveal. Suggested by @dddinary in Extension point: current-conversation user-message outline and reveal-by-ID #98.docs/extensions/reference/limits.md.Update
You must restart the server after you update (Menu → Restart server). Run
npm installfirst: the dependencies changed. To get a new Node.js, close the launcher and start it again: Restart server keeps the Node.js that runs now.Full changes: v0.19.0...v0.20.0
Review
Final review of the combined branch: 3 code reviews (security, server, web and docs), 3 live tests (UI on a copy, agent features on the live server, install on 6 Node versions), and a fresh review of the fixes. Every blocker, high, and medium finding is fixed with a test that fails on the old code.
npm test: 518 of 518 pass. Docker was not built: its daemon does not run here.