Repository navigation
Releases: WPTK/Kipple
Release list
v0.8.0-beta.5
This beta adds a Gazette layout that lays a list out as a newspaper, a read rate and a per-feed sheet on the Stats screen, tile comparisons with the previous period and a Months range, and spreads each feed's refresh across the interval. It records how many new items each feed brings in per day, which needs a one-time database migration to schema 18; the first start writes a snapshot you can restore to go back. It also hardens restoring a backup from the setup wizard and keeps more safety copies of a replaced library.
Added
- Stats: tapping a feed under Sources opens a sheet with that feed's numbers over the same range: items read, active time and days with reading (each compared with the earlier period on a tap), its daily or monthly chart, opens, average read, quick bounce, opened original, stars and reading habits.
GET /api/stats/summarytakesfeed=<id>for one feed's summary. (#37) - Stats: each feed under Sources shows its read rate, the share of the new items that arrived on complete days that you read, and the feed's sheet shows it too, with the counts behind it on a tap ("31 of 50 new"). A feed's first batch of articles does not count. A feed with nothing new, or a stretch with no complete record, shows a dash instead of 0%.
GET /api/stats/summaryreturnsread_rate,read_rate_fromandread_rate_to, and each source has its ownread_rate. (#37) - A Gazette layout lays a list out as a newspaper: a front page led by the newest picture from your Favorites, then section pages by folder or feed, ending with "That's the Gazette." It is always newest first, whatever order the list is set to; a story you read fades in place instead of leaving the page; in the list,
jandkfollow the page's reading order. Mark above and below are off there, because the pages are not in date order. Pick it from the layout menu or as the device default, and rename the paper under Settings, Lists and reading. (#39) - Statistics: the database now keeps which new unread items each feed brings in per day, which the read rate is built on. A feed's first fetch is its backlog and is not counted. It adds schema 18 (a small table). Going back to an earlier version means restoring the pre-migration snapshot the first start writes, as with every migration. (#37)
- Stats: tap a summary tile to see how that number compares with the previous period (for example "+18% from 120") and which period it was compared with; tap again to hide it. A new Months range shows one bar for every month on record. (#37)
Changed
- Feeds that were fetched together, for example after a restore, after the server was down, after an import or after refreshing everything, now spread over the refresh interval after one fetch instead of staying bunched together. Each feed gets its own fixed time within the interval, different on every server; one fetch after such a burst may come sooner than the full interval while a feed moves to its time. After a fetch, the next one waits out any cache time the feed asked for that is longer than the interval; lowering the refresh interval can still bring a feed in once before its last cache time ran out. (#289)
- The Settings and setup screens tell you to add a Google Reader compatible account, naming no particular app. (#292)
- Safety copies of a replaced library (
backup/pre-restore-*) from the last 30 days are kept, up to ten, along with the newest three of any age, so a few resets or restores in a row can no longer delete a recent one. - Reset Kipple and start over: the confirmation dialog, the page that waits for the restart and the docs now say plainly that, until setup is finished, anyone who can reach Kipple can claim it, so setup should be finished right away.
Removed
GET /api/resetno longer reportspublic_address_set: the reset dialog always warns that setup stays open until the new account is created.
Fixed
- A feed's RSS ttl now still counts after the server answers "not modified" or sends the same document again, instead of only after a changed document. A feed last fetched by an earlier version may wait up to its last cache time (at most a day) between fetches until its content next changes. (#289)
- Marking articles read or unread while offline, by opening, swiping, key, scrolling or a bulk mark, now moves the unread counts in the sidebar and badges at once. (#253)
- Closing and reopening the app while offline, with changes still waiting to be sent, keeps the unread counts those changes moved, instead of going back to the counts from before them. (#253)
- A restore whose database redefines a table or index under its real name (other columns, a virtual table, generated columns, or a dropped UNIQUE, a changed CHECK, DEFAULT or foreign key) is refused when it is checked, instead of being accepted and then stopping Kipple at every start.
- Setup wizard restore: an upload that sends nothing for two minutes, averages under 32 KB/s after the first two, or runs longer than two hours or than its size at 128 KB/s (whichever is longer, but never over four hours) is stopped, so it cannot hold the one restore slot for long; the page then says why. A refused upload expires after an hour, like one never confirmed.
- Statistics: comparing a tile with an earlier period no longer reads days when statistics were off, that you deleted, or that a restore replaced, as zero reading ("up from 0"); it says "Not enough history" instead, and Active time waits for the day reading time was first recorded. The comparison also counts complete days only, so today is left out of both periods.
Security
- Setup wizard restore: an uploaded backup belongs to the page that uploaded it, from its first byte, through a random key the page sends with every restore call. Another browser that can reach setup no longer sees its summary or feed list, cannot confirm it with a password of its own, and cannot cancel it, and a cookie set by another site on the same domain cannot make the owner's page show or confirm an upload it did not start.
Container image
ghcr.io/wptk/kipple:0.8.0-beta.5
ghcr.io/wptk/kipple@sha256:fc5a549808b7b725e3df2b9fd58e86e6a14bf0d3ede0bb2fe3d2381856d0e85a
Verify the signature (cosign 3 or later). The identity names this release's tag, so the check also fails for another release's image:
cosign verify ghcr.io/wptk/kipple:0.8.0-beta.5 \
--certificate-identity 'https://github.com/WPTK/Kipple/.github/workflows/release.yml@refs/tags/v0.8.0-beta.5' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Software bill of materials (SPDX, one document per platform): kipple-0.8.0-beta.5.sbom.json, sha256 24b39cc5577baedd4c593e8eaec081825dc1c43506c2626289573ec014507326
To compare, regenerate it from the image with the same buildx version and check the command printed something (the sha256 of empty input, e3b0c442..., means a tooling error, not tampering):
docker buildx imagetools inspect ghcr.io/wptk/kipple@sha256:fc5a549808b7b725e3df2b9fd58e86e6a14bf0d3ede0bb2fe3d2381856d0e85a --format '{{ json .SBOM }}' | sha256sum
The SBOM file is signed too (kipple-0.8.0-beta.5.sbom.json.sigstore.json, attached next to it; cosign 3 or later, same identity):
cosign verify-blob kipple-0.8.0-beta.5.sbom.json --bundle kipple-0.8.0-beta.5.sbom.json.sigstore.json \
--certificate-identity 'https://github.com/WPTK/Kipple/.github/workflows/release.yml@refs/tags/v0.8.0-beta.5' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
v0.8.0-beta.4
Added
- Reset Kipple and start over: Settings > Account & Devices > Reset Kipple erases the feeds, folders, history, settings and account and returns Kipple to setup, where you create a new account or restore a backup. It asks for your password and for you to type
reset kipple, keeps a safety copy of the old library inbackup/pre-restore-*(restorable withkipple restore), keeps your public address, allowed host names, trusted proxies and Cloudflare Access settings, and restarts through your restart policy while the page waits. If your compose file or.envsetsKIPPLE_USERNAMEandKIPPLE_PASSWORD, Kipple ignores them until you create a new account. Only the newest three safety copies are kept, so repeated resets or restores push older ones out: export a backup first if you want one you keep. A provably empty database is never kept as a safety copy. A restore or reset left pending for more than seven days is discarded instead of applied. (#283) - Restore a backup from the setup wizard: on a new server with no account yet, the first screen offers "Restore from a backup". Upload the export zip with a progress bar, check its date, counts and the account you will sign in as, then restore everything (Kipple restarts to apply it) or only the feeds; an OPML file from any reader works as feeds only. The zip is checked before anything changes, the public address, allowed host names, trusted proxies and Cloudflare Access settings of the backup are dropped because they describe the old server, and this server's own are kept, and a new password can be set (it is required when the account's sign-in would not work from here).
kipple restoreand its messages now name the Kipple version that made a backup instead of internal numbers, and it refuses a zip with more than ten files. (#246)
Changed
- Developer tooling:
web/scripts/site-shots.mjs --readme <dir>captures the four screenshots the README shows, andnode scripts/changelog.mjsno longer pins an image tag inREADME.md, which now names none.
Fixed
- The reading font now styles articles only. Lists and the sidebar keep the system font, and a new per-device setting, Use it everywhere (off by default), applies the reading font to them as well.
Container image
ghcr.io/wptk/kipple:0.8.0-beta.4
ghcr.io/wptk/kipple@sha256:9510845f3a2114e25d508a32e57213b84e98e22839e7bb09f0acc1c8cfac2ec3
Verify the signature (cosign 3 or later). The identity names this release's tag, so the check also fails for another release's image:
cosign verify ghcr.io/wptk/kipple:0.8.0-beta.4 \
--certificate-identity 'https://github.com/WPTK/Kipple/.github/workflows/release.yml@refs/tags/v0.8.0-beta.4' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Software bill of materials (SPDX, one document per platform): kipple-0.8.0-beta.4.sbom.json, sha256 3ea2598fd6f396a6170048b66db24c064ec4f8b4f5ced305f9fe8a7b19f0f114
To compare, regenerate it from the image with the same buildx version and check the command printed something (the sha256 of empty input, e3b0c442..., means a tooling error, not tampering):
docker buildx imagetools inspect ghcr.io/wptk/kipple@sha256:9510845f3a2114e25d508a32e57213b84e98e22839e7bb09f0acc1c8cfac2ec3 --format '{{ json .SBOM }}' | sha256sum
The SBOM file is signed too (kipple-0.8.0-beta.4.sbom.json.sigstore.json, attached next to it; cosign 3 or later, same identity):
cosign verify-blob kipple-0.8.0-beta.4.sbom.json --bundle kipple-0.8.0-beta.4.sbom.json.sigstore.json \
--certificate-identity 'https://github.com/WPTK/Kipple/.github/workflows/release.yml@refs/tags/v0.8.0-beta.4' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Kipple 0.8.0-beta.3
Added
- The public URL, the allowed host names, the trusted proxies and Cloudflare Access are now settings: Settings, Account & Devices has an Address and access section for all four, the setup wizard asks for the public URL, and every change applies at once without a restart.
KIPPLE_PUBLIC_URL,KIPPLE_ALLOWED_HOSTS,KIPPLE_TRUSTED_PROXY_IPSandKIPPLE_ACCESS_TEAM_DOMAIN/KIPPLE_ACCESS_AUDnow only give their setting its first value: on upgrade, what they say is stored as the setting, and after that Settings decides (a variable that disagrees is named in a warning at start). On upgrade, a name listed only inKIPPLE_ALLOWED_HOSTSis added once to the allowed host names already saved in Settings, so every name allowed before stays allowed; after that Settings alone decides, and a name you remove there stays removed. Changing the trusted proxies or Cloudflare Access asks for your web password. While the account has no web password and signs in through Cloudflare Access, Access cannot be changed or turned off until a password is set. (#265) - Each feed and folder can have its own order (newest or oldest first) and the view it opens in (Unread or All), set from the list header's options menu or the feed and folder editors. A folder's choice reaches its subfolders and their feeds, and like the layout it is kept per device. (#38)
- Filters: Only show matching keeps the articles that contain a rule's words and mutes the rest, which stay in Muted and can be restored. Narrow it to a feed or a folder to leave other sources alone. (#38)
- A web page or site address added through the Reader API (
quickadd,subscription/edit) or an OPML import now works: its first fetch finds the feed the page links and makes it the subscription's address, and the feed is fetched right after; when that feed is already subscribed, the new entry is removed instead of becoming a duplicate, and the existing feed takes its folder and title. Subscribing still makes no network request during the call. Editing a feed's address to a web page uses the feed the page links, or says the page links several feeds or none. (#266) - Reader API: browser-based clients on another origin can use it. Every Reader API response allows any origin (no credentials: the API authenticates by token, never by cookie), and an
OPTIONSpreflight is answered204without signing in. The web app's own API is unchanged. (#266) - Reader API: items carry the article as
content.contentas well assummary.content, so a client that reads either one gets it. With compression the second copy of an article up to about 30 KB costs almost nothing; a longer one, such as a full-text extraction, about doubles on the wire. (#266) - A reading-time filter in the list header (5 min or less, 6 to 15 min, over 15 min) shows only articles of that length; Mark all as read then marks only what the filter shows. (#38)
- Responses are gzip-compressed for clients that accept it: JSON, text, scripts and styles of 1400 bytes or more, never images, archives or the live event stream. A page of 50 Reader API items with articles of typical length is about a quarter of its uncompressed size, and the web app's start-up data about an eighth. (#266)
- Settings, Account & Devices, Devices can clear every per-list setting (layout, order and view) of this device at once. When there are too many to save, the notice points there.
Changed
- The add feed dialog says why an address did not work, in plain words: not a web address, a web page that links no feed, not a feed or a page, the site could not be found or answered with an error, the site took too long, or the address is on a private network. For a private address it offers "Allow addresses on my own network" and adds the feed with that setting on. Enter in the address field adds the feed. (#266)
- A feed you add without a title is named with the title the feed gives itself as soon as its first fetch finishes, and the web app shows that name right away. Until then the feed is shown by its address, in the web app and in sync apps alike. Every feed name, whether it comes from the feed, an OPML file, a sync app or the web app, is cleaned up the same way: a title escaped twice or written over several lines reads as plain text, control and invisible characters are dropped, a name that is blank once cleaned counts as no name, and a very long name is cut to 200 characters without splitting a character. A title you give the feed, when you add it or later, is kept when the feed changes its own title. The one exception: if the title you give at add time is exactly the feed's own title at its first fetch, Kipple keeps following the feed's title from then on. On upgrade, a feed whose title is just its site's domain name loses that title if it has never fetched, or if it is enabled, in which case its next fetch reads the feed again in full and stores the feed's own title if it has one; a disabled feed that has fetched before keeps its title. An OPML export then never writes that domain name as a name, and importing the file again does not keep it as the feed's title. The upgrade takes the usual pre-migration copy of the database first. The Add feed dialog says when the title will be filled in from the feed. (#255)
- The free-space check before a database upgrade reserves room for a migration that rebuilds a table, as this release's does: twice the database size plus 64 MB on the database's volume instead of once, plus 1.1 times the database for the pre-migration snapshot (about 3.1 times the database plus 64 MB when both share a volume, as in the default
/datalayout). When it refuses, the message now says how much more space to free. (#260) - Reading stats and Feed Health treat every sync app the same: stats from any Reader API client are recorded as
api, existing rows that named a particular app are rewritten toapiwhen the database upgrades (schema 15), and Feed Health shows one "last seen" time for sync apps. After upgrading, reload the web app (or reopen the installed app) so Feed Health loads the new version. Going back to an earlier version means restoring the pre-migration snapshot. (#260) - When Kipple runs without a password (open mode) and refuses the address a page was opened with, the message now says how to allow that name: open Kipple by its IP address and add the name under Allowed host names in Settings. The deploy guide explains which names open mode answers and why a single-word or
.local-style name has to be allowed by name: any device on your network could answer it with your computer's address. (#254) - OPML export writes a folder's subfolders before its own feeds, the order the web app shows; a library without subfolders exports exactly as before. (#244)
- On a feed or folder list, the oldest-first button sets that list's own order; on Unread, All, Starred and Muted it sets the order for the device. (#38)
- Reader API:
mark-all-as-readon the unread (or kept-unread) stream now marks every unread item read, as on the reading list; it used to change nothing. (#256) - Search: a search that matches more than 75,000 items is refused as too broad (
422 search_too_broad) after one bounded walk of its matches, on every page, instead of running until the 500 ms budget, so a common word no longer works on a quiet machine and fails on a busy one; at 150,000 items the refusal takes about 100 to 200 ms. (#229) - Sync app wording is neutral: the Account and setup screens, the command-line password messages, the Reader API settings text and the unread-cap warning no longer name particular apps, because any client that speaks the Google Reader API can sync.
- Adding a feed accepts an address as people type or paste it, everywhere a feed address goes in (the add dialog, the Reader API, OPML import, editing a feed's address):
example.com,example.com/feed,//example.com/feed,feed://andfeed:https://links (andpcast:,itpc:,podcast:,rss:), surrounding spaces, a#fragmentand an uppercase scheme or host. An address without a scheme getshttps://when it starts with a name under a real top-level domain (example.com,you.github.io) andhttp://when it starts withlocalhostor an IP address; a relative path, a file name or a local name such asnas.lanstill needs its scheme typed. (#266)
Fixed
- Moving a folder whose stored name is longer than 100 characters or holds a control character now answers
400 bad_request(rename it to move it) instead of a server error. (#236) - Moving a folder saves its new place and the new folder order in one step, so a move can no longer half-complete, and rapid moves or a refresh in between no longer show a folder in the wrong place.
POST /api/reorderaccepts{id, parent_id}in itsfolderslist to move a folder; a bare folder id still keeps its parent. (#236) - Reader API:
stream/contents/feed/<feed URL>now answers with the feed URL exactly as requested in the responseid, instead of with the//after the scheme reduced to one slash. (#256) - Reader API: a write whose
Tedit token carries surrounding whitespace, such as the newline that ends thetokenresponse, is now accepted instead of answered 401. (#256) - An upgrade that fails the same schema migration on every restart keeps one pre-migration snapshot for that step instead of adding one per start, so the snapshot the previous version needs is no longer pruned. When an older version refuses a newer database, the message names the snapshot in the backup folder that version can open, and it no longer tells you to run the version you are running.
KIPPLE_PUBLIC_URLwith an internationalized host (such ashttps://rss.bücher.example) no longer stops the start: it is stored in itsxn--form, and a value that is not used because the setting is already saved is never judged.
Security
- Reader API: a folder label longer than any real folder path (807 characters: eight names of 1...
Kipple 0.8.0-beta.2
Changed
- Outgoing requests (feeds, article extraction, the image proxy) now reuse connections to the same host instead of opening a new one per request, which saves TLS handshakes on pages with many images.
- Large libraries answer faster: the feed list's starred counts, folder and Starred article pages, and mark-all-as-read read only the rows they need (two new indexes, added by an automatic migration on first start), and a refresh no longer re-counts a feed that is within its retention limit.
- Feed ingest allocates less: a UTF-8 feed body is no longer copied to rewrite its XML declaration, and word counting no longer builds a slice of every word.
- The article list no longer redraws every row when you mark one article read.
Fixed
- Shutting down now waits for in-flight image thumbnail jobs to finish before the image cache and database close, so a stop during a transcode no longer cuts one off mid-write.
Container image
ghcr.io/wptk/kipple:0.8.0-beta.2
ghcr.io/wptk/kipple@sha256:31eedc0b0ab80457ed80e10d134f134e9c8e210433ea22c8f58725724d432dd6
Verify the signature:
cosign verify ghcr.io/wptk/kipple:0.8.0-beta.2 \
--certificate-identity-regexp '^https://github\.com/WPTK/Kipple/\.github/workflows/release\.yml@refs/tags/v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(alpha|beta|rc)\.[1-9][0-9]*)?$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Software bill of materials (SPDX, one document per platform): kipple-0.8.0-beta.2.sbom.json, sha256 a755c738e927c5d251af9c422cf968091109a6774cfe601dccd51b55afda60b0
To compare, regenerate it from the image with the same buildx version and check the command printed something (the sha256 of empty input, e3b0c442..., means a tooling error, not tampering):
docker buildx imagetools inspect ghcr.io/wptk/kipple@sha256:31eedc0b0ab80457ed80e10d134f134e9c8e210433ea22c8f58725724d432dd6 --format '{{ json .SBOM }}' | sha256sum
Kipple 0.8.0-beta.1
Added
- Folders can nest, up to 8 levels: a folder may sit inside another, and a folder's list, search, mark-all-read, unread count and folder filters cover its subfolders too. Reader API clients see each nested folder as one folder named by its full path (
Tech/Apple), holding that folder's own feeds; a client that files a feed underTech/Applecreates both folders. Deleting a folder deletes its subfolders, and their feeds move to Uncategorized. A library without nested folders looks exactly as before to every client. (#209) - The web app shows nested folders as a tree: the sidebar and the Feeds screen indent subfolders, collapse them per device and count each folder's unread over its subfolders; a folder can be created inside another, moved by dragging or with Move to…, and every folder picker lists folders by their full path. A folder's layout carries down to its subfolders. OPML import can move feeds you already have into the file's folders (off by default). (#209)
Changed
- OPML import and export keep the folder tree: nested outlines become subfolders (up to 8 levels; a feed nested deeper, or in a folder whose name cannot be stored, goes into the nearest folder above it and the report says so), folders with the same name under different parents stay apart, and the export writes subfolders as nested outlines. A new import option moves feeds you already have into the file's folders (
kipple import -move-existing,POST /api/opml?move_existing=true). (#209)
Fixed
- Closing a dialog, the shortcut overlay or a row's More actions menu with Escape now puts the keyboard back on the control that opened it, instead of dropping it to the top of the page; Export backup and Apply retention stay focusable while they work so the backup dialog can return there too.
- A refresh no longer slows down as the library grows: trimming a feed to its retention limit read every item in the database, once for each feed trimmed, so refreshing 500 feeds in a 150,000-item library took about 23 seconds instead of under 4, and the app's own changes (a star, a mark as read) waited behind it. (#237)
- Lowering a retention limit on a large library trims about three times faster: on 150,000 items, trimming 36,000 of them takes about 24 seconds instead of 75, and each step of the trim holds up a star or a mark as read for about a second instead of several. (#228)
Container image
ghcr.io/wptk/kipple:0.8.0-beta.1
ghcr.io/wptk/kipple@sha256:cfe291f0de8dc74db351b798149626b616001d2d39e45fd1276dc22b1cc63e29
Verify the signature:
cosign verify ghcr.io/wptk/kipple:0.8.0-beta.1 \
--certificate-identity-regexp '^https://github\.com/WPTK/Kipple/\.github/workflows/release\.yml@refs/tags/v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(alpha|beta|rc)\.[1-9][0-9]*)?$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Software bill of materials (SPDX, one document per platform): kipple-0.8.0-beta.1.sbom.json, sha256 6c2592c881b88fd31c26a0aa4703078a844326dc8096963a2058c742ae6b1456
To compare, regenerate it from the image with the same buildx version and check the command printed something (the sha256 of empty input, e3b0c442..., means a tooling error, not tampering):
docker buildx imagetools inspect ghcr.io/wptk/kipple@sha256:cfe291f0de8dc74db351b798149626b616001d2d39e45fd1276dc22b1cc63e29 --format '{{ json .SBOM }}' | sha256sum
v0.7.0-beta.2
Changed
- Dependency updates: the feed parser (gofeed 1.5.0) now reads an author written as
Name <email>into name and email and enforces its response size limit to the end of the body, and the SQLite driver (1.60.1) binds query parameters faster; nothing else changes for you. - A new Kipple's log line
no account yetno longer names the address it listens on, which in Docker is the port inside the container and not the one you published; it now just says to open Kipple in a browser, or to setKIPPLE_USERNAMEandKIPPLE_PASSWORDand restart.
Container image
ghcr.io/wptk/kipple:0.7.0-beta.2
ghcr.io/wptk/kipple@sha256:bc91fad472181efe7b1763816fcac8333e29752c6356bace176691562a184148
Verify the signature:
cosign verify ghcr.io/wptk/kipple:0.7.0-beta.2 \
--certificate-identity-regexp '^https://github\.com/WPTK/Kipple/\.github/workflows/release\.yml@refs/tags/v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(alpha|beta|rc)\.[1-9][0-9]*)?$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Kipple 0.7.0-beta.1
Changed
- Internal cleanup: schema 11 (migration 0011) deletes six settings rows nothing reads any more (
security.open_lan,ui.font_size,ui.font_ui,ui.layouts,stats.api_single_read_is_openandsys.legacy_port) and removes the threeui.*ones from any saved per-device appearance profile; nothing changes for you, and rolling back to 0.6 is by the pre-migration snapshot the first start writes, as with every migration. - A new Kipple opens on the form that creates your account (step 1 of 6 of the setup wizard) instead of asking for a setup code first. Until the account exists only that form,
/api/instanceand/healthzanswer, sign-in says setup is required, and nothing is fetched. - Open mode is one rule now: while the account has no password, a request is allowed when its connection comes from a local address (this computer, a link-local or private network address, or a Tailscale device) and carries no proxy or tunnel header, and the setting "Also allow devices on my local network" (
security.open_lan) is gone. An install that was already in open mode with that setting off (this computer and your tailnet only) now also admits every device on its local network; a stored value of the old setting is ignored and Settings no longer lists it or accepts it. In Docker every connection arrives from the bridge gateway, so Kipple cannot tell your network from the internet there: publish an open-mode Kipple only on your local network or tailnet address, never on a public interface.
Removed
- The setup code is gone: a new Kipple no longer prints a code, writes
/data/setup-tokenor asks for one, and thekipple setup-tokencommand is removed. Whoever reaches an unclaimed Kipple first creates the account, so keep the port on 127.0.0.1 until you have, or create the account fromKIPPLE_USERNAMEandKIPPLE_PASSWORD.
Security
- Open mode no longer has a local-network switch, so an existing open-mode install that had it off (this computer and tailnet only) now lets in every device on its local network without a password, and anything that can reach the published port from a private address; set a password under Settings, Account & Devices, or bind the published port to this computer, if that network is not fully yours. A peer in Tailscale's range (100.64.0.0/10, also carrier-grade NAT and cloud overlay space) is still admitted only when it arrived on this machine's own Tailscale address or reached a private address of this machine, and forwarded or proxy headers are still refused, so a request through a proxy or tunnel never counts as local.
Container image
ghcr.io/wptk/kipple:0.7.0-beta.1
ghcr.io/wptk/kipple@sha256:f729ef461c3ca762b1d1ba4acab8b28ff60245257222ee4060f068d62035ff72
Verify the signature:
cosign verify ghcr.io/wptk/kipple:0.7.0-beta.1 --certificate-identity-regexp '^https://github\.com/WPTK/Kipple/\.github/workflows/release\.yml@refs/tags/v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(alpha|beta|rc)\.[1-9][0-9]*)?$' --certificate-oidc-issuer https://token.actions.githubusercontent.com
Kipple 0.6.0-beta.1
Before upgrading from 0.5: if your install publishes port 7080 and does not set KIPPLE_ADDR, set KIPPLE_ADDR=:7080 in .env first (or move your mapping to 1919). Without it Kipple listens on 1919 behind a mapping for 7080, the container still reports healthy (the health check probes 1919), and the service looks down with nothing in the log. This release removes workarounds found in a review of the whole codebase and fixes what the review proved: the login lockout that a stranger could use to lock the owner out behind a proxy, offline reads that came back unread, and a release workflow that could leave an unsigned version tag. It is a minor bump because it removes things: the pre-0.5 and fallback listen ports, four unused settings keys and the time zone variable's hold on the setting. Read the Removed and Changed entries before upgrading.
Changed
- Settings: reading spacing and list spacing now share one vocabulary, the five steps (Dense, Snug, Standard, Relaxed, Airy), and the reading default is
standard; the first-draft namescompactandcomfortableare still accepted and read assnugandstandard.ui.font_bodynow holds the font id (default,literata,source-serif, ...) in every client, as the web prefs do; an empty value or a display name such as "Inter", stored earlier or sent by an older client, reads as its id. - Stats, Your year and the statistics settings are reworded in plainer, shorter language: empty states, captions, dialog help and setting descriptions lose their explanations, and "events" is now "records" in the export and delete dialogs.
- The
TZenvironment variable now only gives a new install its time zone: on a start where no time zone setting exists yet it is stored as thetzsetting, and from then on the setting alone decides the zone for statistics, the nightly job, backup file names and log timestamps (at the next start). Settings, Account & Devices no longer shows the time zone read-only, the API no longer refuses a change oftzor reportsenv_override, and Kipple no longer logs a WARN at start whenTZand the setting differ. Installs that already have a time zone stored, which includes every install upgraded from before 0.5, keep it whateverTZsays. If yourTZdiffers from the time zone stored in Kipple, statistics and the nightly job follow the stored zone from this upgrade on (on 0.5.0-beta.2 they followedTZ); to check before upgrading,GET /api/settingson 0.5.0-beta.2 shows both thetzvalue andenv_override. - Setup, Recommended feeds: one Select all / Select none button for the whole list instead of a pair in every category, and the language tag (EN) is no longer shown next to each feed.
Removed
- Removed four settings keys that nothing read or showed:
ui.font_size(text size is a per-device choice),ui.font_ui,ui.layouts(list layouts are per-device profile keys) andstats.api_single_read_is_open(reads from sync apps never count as statistics). They no longer appear inGET /api/settings,/api/bootstrapor device profiles, andPATCHrefuses them as unknown. Rows an existing database holds for them are left in place and ignored. The unusedcssfield of the Spacing option list is gone too, and the favorites fallback that kept pinned folders on one device is removed (favorites that were kept only in a device's local storage, because the server had refused them, are dropped). - The port handling is finished at its root (0.6.0): the pre-0.5 default 7080 is no longer kept for old installs, and the automatic
:1138fallback is gone too. An unsetKIPPLE_ADDRnow always means:1919, whatever the database says; if that address (or the one you set) is taken, Kipple exits with an error naming it andKIPPLE_ADDRinstead of choosing another port.kipple healthcheckmakes one probe of the address the server would use, and a restore no longer carries a listen port. If your install still publishes 7080 without setting it, setKIPPLE_ADDR=:7080in.env(keeping the7080:7080mapping) or move to 1919 before upgrading to 0.6.0, or Kipple will listen on 1919 behind a mapping for 7080 and look down.
Fixed
- Settings > Appearance & Reading no longer shows Day theme, Night theme and List spacing twice, once as this device's own control and once as an unlabeled account default; only the per-device control is listed.
- Images that a site refuses unless the request looks like a browser are now retried with the same browser User-Agent as feeds, including your custom one if you set it, instead of a separate older built-in string.
- Web sign-in no longer refuses the right password after ten wrong ones: wrong passwords are now slowed per client (five free, then a wait that doubles from two seconds to a minute, cleared by a correct password and forgotten after an hour with no failure), and the client is told apart by one resolver that believes
X-Forwarded-For(the rightmost hop that is not a listed proxy) orCF-Connecting-IPonly from a peer inKIPPLE_TRUSTED_PROXY_IPS. With that list correct, every visitor is a separate client and a stranger cannot slow your key's pacing; all clients do share one password-hashing slot, so enough distinct addresses can still make sign-in answer "busy, try again" (503), and even one persistent guesser on an address people really share (an unlisted proxy, Docker Desktop's gateway, carrier NAT) can do the same to those people, but nothing is ever a lockout or a 429. - Refreshing all feeds, importing or exporting OPML, and the status and feed-health views now answer 503 with Retry-After while the search index rebuilds, instead of a 500.
- Offline: an article you read or starred while offline no longer comes back as unread or unstarred when the app is reopened offline, and once the queued changes are sent the open lists show them without waiting for the event stream.
- The
/_statussign-in now says "Kipple is busy. Try again in a moment." when sign-in answers 503, instead of "Sign-in failed." (it still looked for a 429 that sign-in no longer returns). - Kipple no longer logs the hourly "proxy headers from an untrusted peer" warning for requests that come through Tailscale Serve, which is a correct setup that is meant to stay out of
KIPPLE_TRUSTED_PROXY_IPS. The warning uses the same Tailscale Serve test as open mode, and it still fires for any other untrusted proxy sendingX-Forwarded-For,CF-Connecting-IPorX-Forwarded-Proto.
Security
- Open mode: the "Also allow devices on my local network" setting no longer lets in a peer from the Tailscale range (100.64.0.0/10, also carrier-grade NAT and cloud overlay space) that reached Kipple on a CGNAT, public or unknown local address; such a peer is admitted only when it arrives on this machine's Tailscale address or on a private-range address (a LAN or a container's bridge), and the setting's text and docs now say exactly that. (#175)
KIPPLE_TRUSTED_PROXY_IPSnow accepts CIDR ranges as well as single addresses, and anX-Forwarded-Forchain is read from the right, so a client cannot choose its own address by writing the leftmost entry; forwarding headers from a peer outside the list are still ignored. List a range such as a Docker network only when the published port is reachable by the proxy alone: otherwise any client inside the range can write its ownX-Forwarded-FororCF-Connecting-IPand be believed.
Container image
ghcr.io/wptk/kipple:0.6.0-beta.1
ghcr.io/wptk/kipple@sha256:a8c1be43467f340275f7eaa6f092691fe6fe8e6ef3742f3319740236b2ccc6f4
Verify the signature:
cosign verify ghcr.io/wptk/kipple:0.6.0-beta.1 --certificate-identity-regexp '^https://github\.com/WPTK/Kipple/\.github/workflows/release\.yml@refs/tags/v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(alpha|beta|rc)\.[1-9][0-9]*)?$' --certificate-oidc-issuer https://token.actions.githubusercontent.com
Kipple 0.5.0-beta.2
Fixed
- iOS Safari and the installed app tinted and softened the status-bar strip above the list header, which showed as a blur until you scrolled; a solid, fixed cover in the page colour now gives it one thing to sample. (#163)
- Offline, lists and screens no longer sit loading forever after the connection drops: what the device kept opens, anything else shows its error with Try again and loads by itself when the connection is back, and opening or starring an article offline is saved and sent later instead of being lost on reload. (#167)
Container image
ghcr.io/wptk/kipple:0.5.0-beta.2
ghcr.io/wptk/kipple@sha256:8890f1244f07ad1a7e35f9ec826a32c853652ff39d8f2eee25d046c1f7682bb0
Verify the signature:
cosign verify ghcr.io/wptk/kipple:0.5.0-beta.2 --certificate-identity-regexp '^https://github\.com/WPTK/Kipple/\.github/workflows/release\.yml@refs/tags/v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(alpha|beta|rc)\.[1-9][0-9]*)?$' --certificate-oidc-issuer https://token.actions.githubusercontent.com
Kipple 0.5.0-beta.1
Added
- Build info: Settings > About shows the version, commit, build date, Go and SQLite versions, database schema, uptime and sign-in mode of the server, plus this page's own build and service worker, with a "Copy debug info" button that puts the same facts (no username, address or data path) into a plain-text block for a bug report;
kipple version -vprints the same details (plainkipple versionstill prints just the version); the image gains the OCI labelscreated,url,documentation,base.nameandbase.digest; a page loaded before the server was rebuilt now says "A newer version of Kipple is ready" with a Reload button (GET /api/bootstrapgainsweb_build,GET /api/aboutis new); after an upgrade "What's new" shows the changelog sections since the last version you saw, once for the whole account (new hidden settingui.whats_new_seen); and a database written by a newer Kipple is still refused, but the message now names the version that last opened it and how to recover. Nothing checks for updates or contacts anything. - Signed multi-arch images: pushing a release tag now builds, scans and smoke-tests a linux/amd64 and linux/arm64 image and publishes it to
ghcr.io/wptk/kipplewith a cosign signature and build provenance; a prerelease never moveslatest. - First-run setup without an
.envfile: a server started without an account prints a one-time setup code on standard error (kipple setup-tokenshows it again), and the browser uses it to create the account with a password, with Cloudflare Access, or with no password at all (open mode: only from this computer or Tailscale, or also the local network when you allow it, which Kipple in Docker needs because it cannot tell this computer from the network there). New endpoints back the setup wizard, onboarding ("Run setup again") and the recommended feeds. Creating the account fromKIPPLE_USERNAMEandKIPPLE_PASSWORDworks as before. - A first-run setup wizard in the web app: enter the setup code Kipple prints at start (or open its link), create the account with a password, no password behind Cloudflare Access, or no password at all (open mode, with a plain warning that it is only for this computer or Tailscale and the reasons a network is refused), then choose a time zone (preselected from the browser, searchable), a day and night theme with a live preview, import an OPML file, pick recommended feeds and optionally create the API password for sync apps. Every step after the account can be skipped, and Settings, Account & Devices has Run setup again.
- Sign-in for an account without a password (open mode) is automatic from an address Kipple allows and otherwise explains, in plain words, why it refused and how to get in, instead of showing a broken screen; Settings no longer offers Sign out for such an account, since Kipple would sign the browser straight back in.
- The setup wizard's Recommended feeds step now offers a real starter list of ten feeds in six categories (Design & UI, Art, Tech, Automotive, Books, Aviation), all ticked by default and each one skippable;
starter/feeds.jsonis the file to edit, andscripts/check-starter-feeds.mjschecks that every feed is still live.
Changed
- The default port is now 1919 (1138 when 1919 is taken and
KIPPLE_ADDRis unset). An existing install withKIPPLE_ADDRunset keeps listening on 7080 through 0.x, with a warning at every start; that fallback goes away at 1.0, so setKIPPLE_ADDR=:7080or move to 1919. WithKIPPLE_ADDRunset,kipple healthchecktries 1919, 7080 and 1138. - New installs record reading statistics and run the nightly job in UTC until you choose a time zone; existing installs keep America/New_York. A
TZenvironment variable, when set, now governs statistics and the nightly job too, and the in-app time zone is then shown read-only. Backup download names use the same zone. - Settings, Account & Devices shows the time zone read-only, with the reason, while the TZ environment variable is set, as the setup wizard's time zone step does.
- Statistics: a read now needs reading time, not just a scroll. An opened article counts as read after 10 seconds of active reading, or after a scroll past a quarter of it with at least 3 seconds of active reading; a quick flick through an article, or paging past it with next and previous, is now a bounce. The rule is applied whenever the statistics are computed, so your existing history is reclassified too: items read, days with reading, streaks, average read length and quick-bounce rates can go down, and nothing stored changes. Articles opened before reading time was recorded still count as read, since there is no way to tell, and the Stats screen and the data dictionary now say so. (#120)
Fixed
- Settings > About (and its debug text) reports open mode as "open (no password)" instead of "Cloudflare Access only", and shows the time zone in force now (
TZ, else the zone chosen in Settings) instead of the one the server started with. (#131) - The pull-and-run compose file (
docker-compose.pull.example.yml, and its copy in the README) names the containerkipple, sodocker logs kippleanddocker exec kipple /kipple setup-tokenfrom the quickstart work. (#124) - The statistics data dictionary shipped in every export now says that a set
TZdecides the local date and hour fields and the export'stz, not only the time zone setting; the compose example and the design notes describe the current time zone and port rules. (#132) kipple healthcheckwith aKIPPLE_ADDRthat names a host (such askipple-box:1919) no longer reports a healthy server as unhealthy in setup or open mode, where the Host check answered it 421.kipple passwordrefuses the example passwordchange-me, like the setup wizard andKIPPLE_PASSWORDalready did.- Release images:
latest,X.YandXonly move onto the highest stable tag (an older patch or a re-run no longer moves them backwards), a publishedX.Y.Zimage can never be re-pointed at a new digest, tags with leading zeros are refused, the image'sversionlabel is the real release version instead ofsha-<commit>, the binary in an official image now reports its build date, and thebase.digestlabel is read from the Dockerfile'sFROMline instead of a second copy that would go stale. (#125, #126, #127) - The reading font can be chosen again in Settings > Appearance & Reading (every font, grouped, with a sample paragraph in the chosen font), from the Aa button on the Search screen as well as above every other list and article, and in the setup wizard, whose step 4 is now "Look and feel": a day and night theme plus the reading font, previewed at once and saved as the default for every device, with Skip putting this device's font back. It had been left only in the Aa menu since 0.2.0-alpha.2.
- Restoring a backup from an install on the old port 7080 into a new install that was not set up yet keeps port 1919, instead of moving the server to 7080 behind a 1919 port mapping while the health check still passed; restore says what it kept. (#139)
- Setup: a wrong setup code is tied to its field for screen readers, the password checks on the account step speak once a field is left rather than on every keystroke, and an OPML file over the server's 8 MB limit is refused before it is uploaded (also in Import OPML). (#145, #146, #147)
- Open mode: a browser that does not keep the session cookie now gets a message about cookies instead of signing in over and over, and Try again after the account got a password moves to the sign-in form. (#140, #141)
- The signed-out screen no longer shows the password form when Kipple cannot be reached or answers with a server error: it says so and offers Try again (the form still appears for an older server), and the sign-in form follows a Kipple that has gone into open mode. (#134)
- Setup: Back and Skip are disabled while a step is saving, a second API password warns before it replaces the one shown once, the step 2 password is dropped when setup ends elsewhere, the typed user name survives a timed-out setup session, and the step 7 password field no longer flashes for an account without a password. (#142, #143, #144, #148, #149)
- Setup: trying themes in the look step is only a preview on this device and no longer writes theme overrides to the device profile, whether you continue, skip or end setup. (#135)
- Setup: Skip the rest of setup on the time zone step now keeps the zone shown instead of leaving Kipple on UTC, and a stale /welcome address opened before the account exists no longer carries the new account past that step. (#133)
- Feed icons: the favicon finder no longer tries an icon link whose resolved address is not a valid URL (such as an unbracketed IPv6-style host); it is skipped like any other unusable link. (#157)
Security
- In open mode an open live-update stream is closed as soon as "Also allow devices on my local network" is turned off (or another security setting stops admitting the device), and at the next heartbeat when the device moves, instead of staying open.
- In open mode a device in Tailscale's address range counts as a tailnet device only when its connection arrives on this machine's own Tailscale address; the same range arriving on the local network interface is treated as a LAN device.
- Open mode no longer takes a request for Tailscale Serve because its Host ends in
.ts.net: a same-machine reverse proxy that passes the client's Host through could otherwise hand a remote client a session and a Reader API password. Serve is now recognised only by whattailscaleditself sends (one tailnetX-Forwarded-Foraddress, a matchingX-Forwarded-Host,https) on a machine that has a Tailscale address. (#128) - During setup Kipple answers only requests addressed to an IP address, localhost, a single-word name, a
.localhost,.local,.lan,.home.arpa,.internalo...