Repository navigation
Releases: Soules-Studio-Ltd/HEYCliKit
Releases · Soules-Studio-Ltd/HEYCliKit
Release list
HEYCliKit 0.6.0
This release stops one row the CLI printed sparsely, or of a kind the package does not model, from failing the whole page, Screener list or watch line it sits in, makes optional the four fields where a zero value would have been a lie, makes every date decode on macOS 15, keeps every value out of a decoding failure's description, and says why a sign in was not completed.
- A single posting's topic id is optional, source breaking.
Posting.Single.topicIDwasTopicIDand is nowTopicID?. The CLI derives a posting'stopic_idfrom its app URL and leaves the key out when it has none, andTopicID(0)is a broken link rather than a topic, so a box read can now hand an app a row that names no topic instead of failing the page it sits on. An app unwraps it where it opened a topic from a row, and can still open the row itself throughappURL. Inside a watch line nothing changes: the CLI prints notopic_idon a posting there and the line's ownthread_idstill fills it, so a change line's posting carries the topic it always did. - A posting's box id is optional, source breaking.
Posting.Single.boxID,Posting.Bundle.boxIDand thePosting.boxIDthat forwards them wereBoxIDand are nowBoxID?. The CLI leavesbox_idout at zero and zero names no box. A box is asked for by its kind and never by this id, so an app that only reads a box reads nothing different. - A bundle's own URL is optional, source breaking.
Posting.Bundle.bundleAppURLwasURLand is nowURL?, since the CLI leavesapp_bundle_urlout when a bundle has no address of its own. The bundle'sappURL, which opens its row, is unchanged and still required. - A Screener entry's topic id is optional, source breaking.
ScreenerEntry.topicIDwasTopicIDand is nowTopicID?, for the same reason as a posting's. An entry stands for the sender rather than for their mail, so approving and denying are unaffected, and an app that related an entry to a posting unwraps it. - A posting of any other kind, source breaking.
Postinggained a third case,other(Posting.Other), for anykindbesidestopicandbundle, so an exhaustiveswitchoverPostingno longer compiles until it handles.otheror has adefault.Posting.Othercarries the fields every posting shares, read exactly as a single posting and a bundle read them, andkind, the CLI's own string verbatim, which is empty when the CLI printed nokindat all. The forwarding properties onPostingread an other posting as they read the other two, so a list that only shows a subject and a sender draws it unchanged. This is a behaviour change: a posting of any other kind used to fail the whole page,entryincluded even though hey-sdk's own schema declares it, and inside a watch it made anaddedorupdatedlineunrecognized, which told an app nothing about a mail that had arrived. Both now decode, and the app decides whether and how to draw the row. Akindthat is present but not text still fails the posting. - An absent key is the CLI's zero value. The CLI leaves a key out instead of printing an empty string, a zero or an empty list, and the package required most of them. This is a behaviour change:
name,contacts,addressed_contactsandvisible_entry_counton a posting,name,email_address,initialsandavatar_background_coloron a contact, andname,email_address,subjectandsummaryon a Screener entry now decode to"",0or[]where they used to fail the whole page, Screener list or watch line. The real world triggers are ordinary: a Bcc only or undisclosed recipients mail prints noaddressed_contacts, a blank subject prints noname, a sender whose name has no letters prints noinitials, and a sender HEY knows only by a bare address prints neithernamenorsummary. It mattered most on a watch: a decoding failure anywhere inside a line made the whole lineunrecognized, so anaddedline for a Bcc only mail told an app nothing at all, and it now arrives as the change it is.app_url,active_atandobserved_atstay required, and so do theidof a posting, a contact and a Screener entry: a row the app cannot open, that HEY never observed, or that has no identity, is not a row. - Every date decodes on macOS 15. The package read dates with the
.iso8601strategy, and under it the Foundation that macOS 15 ships refuses fractional seconds. Everyobserved_atand every watch line'satcarries one, so on the package's minimum OS every page read failed and every watch line,readyincluded, wasunrecognized. The shared decoder behind every read and every watch now splits the fraction off itself, parses the rest and adds the fraction back, for anywhere from none to nine digits, and keepsZand a local offset such as+02:00, which is whatexpires_atcarries. On current macOS every date decoded already, and the one visible change there is that a date the package cannot read is now described in the package's own words, which name the field's path and never carry the value. Only the current Foundation was checked, on macOS 26, because no iOS 18 simulator runtime or macOS 15 machine was available. Nothing was run on the older Foundation. The claim for it rests on reading its parser's source in swift-foundation's release/6.0 branch, which shows it reads the date the decoder hands it once the fraction is taken out,Zand+02:00included. - A refused row is dropped and counted.
BoxPagegainedrefusedRowCount. A row the package cannot read at all, one with noid,app_url,active_at,observed_atorcreator, an id that is not an integer, or a row that is not an object, is dropped from the page and counted there. This is a behaviour change: such a row used to fail the whole page.postings.count + refusedRowCountis how many rows the CLI printed, so an app comparing the decoded count against the page size it asked for sees one fewer per refused row, and the cursor is unaffected because it is opaque. A page whosepostingsis absent or not a list still fails as a whole. A watch line whose posting is refused this way is stillunrecognized, since a line holds one posting and there is no page to count it against. - A decoding failure's description never carries a value.
DecodingFailure.descriptionnow names keys, positions and schema tokens only, so an app may log it verbatim without writing anything out of the mailbox to disk. It used to be Foundation's own description of the decoding error, which quotes the character it could not parse, the number it could not represent and the raw value an enum could not read, and a mail account missing a field was described with its id. It is now one of a few sentences in the package's own words, such asExpected String at data[1].version.,The key postings is missing at data.,The value at data[0].status could not be read.orThe output could not be read., the last for output that is not JSON and for a number Foundation cannot represent, which it reports at the root even inside valid JSON, and a value the package refuses on purpose, such as a date or a mail account missing its email address, is described by the refusal alone. This is a wording change: an app that asserted Foundation's phrasing, in a test or anywhere else, sees it change. The CLI's own text is still carried whole inrawText, which is unchanged. - A sign in that was not completed says why.
LoginFailuregainedkind, aCaseIterableLoginFailure.Kindthat istimedOut,accessDenied,cancelledornotClassified, so an app can tell the CLI's own five minute timeout from a sign in the user declined without reading the CLI's text, which carries the sign in address. The package decides it: a cancel the app asked for iscancelledwhatever ending the child came to afterwards, and otherwise exit 3 with the failed envelope's error on stderr reads as timed out or access denied, checked against the CLI's source at 1.4.0, 1.4.3 and 1.6.0. Everything else, a stderr the package stopped past the output ceiling included, isnotClassified.init(exitStatus:standardError:)keeps its signature and classifies the ending by the same rule, never ascancelled, since an ending alone cannot say a cancel was asked for, andinit(exitStatus:standardError:kind:)is added to state any kind.HEYFixtureClientgainedscript(loginNotCompleted:), which scripts a sign in ending with any kind, and a scripted cancel resolves ascancelledby the same rule as a live one. This is a behaviour change:kindtakes part in equality, so a test that compared a cancelled sign in against a failure built with the two argument initialiser now stateskind: .cancelled. Adding a kind later is a source break for an exhaustiveswitch, as adding an error case is, so a new kind waits for a version that is allowed to break. - A sign in failure's description never carries its stderr.
LoginFailureis nowCustomStringConvertible, and its description names the kind and how the child ended, such asThe sign in timed out (exit code 3)., and nothing read from stderr, so an app may log it verbatim. ALoginOutcomeis described through it, so an outcome logged as it is carries no stderr either. This is a behaviour change: the description used to be Swift's own, which printedstandardErrorin full, and with it the sign in address and the machine's install id. The stderr itself is unchanged and still carried whole for the app's local logs. The conformance is source breaking only for an app that declared its ownCustomStringConvertibleconformance onLoginFailure, which it now removes.
HEYCliKit 0.5.0
This release makes every source break the public surface needed while the package is at 0.x, keeps the CLI's words when a failure is printed on stderr, and lets the fixture client fail a watch after its lines.
- A labelled non empty set initialiser, source breaking. The failable initialiser taking a sequence was
NonEmptySet.init?(_:)and is nowinit?(elements:). The unlabelled spelling read as the variadic initialiser, so where nothing fixed the element type,NonEmptySet(ids)compiled and built a set holding one array. An app that builds a set from an array or another sequence writesNonEmptySet(elements: ids), and the variadic spelling is unchanged. - Cancelling a sign in is a method.
LoginHandle.cancelwas a public stored closure and is nowcancel(). A call such ashandle.cancel()compiles unchanged. - A sign in's outcome is a property, source breaking.
LoginHandle.outcomewas a stored closure awaited asawait handle.outcome()and is now read asawait handle.outcome. An app drops the parentheses. It is still decided once and shared, and cancelling the task that awaits it still cancels the sign in. - Releasing a held answer is a method.
HEYHeldAnswer.releasewas a public stored closure and is nowrelease(). A call such asheld.release()compiles unchanged, and a release is still a latch. - The unreadable watch line is spelled the American way, source breaking.
WatchLine.unrecognised(rawText:)is nowWatchLine.unrecognized(rawText:). An app renames the case wherever it matches it, and what it carries is unchanged. - A failed envelope on stderr keeps the CLI's words. When a command or a watch fails and stdout is not an envelope, stderr is now read as one, and a failed envelope there decides the meaning by the same rule as one on stdout, keeping the CLI's
code,errorandhint. This is a behaviour change: hey 1.4.0 prints a signed out read's envelope on stderr with nothing on stdout, soHEYCliKitError.signedOutnow carries the CLI's hint where it used to carry none. A child killed by a signal is still a process failure, a clean exit still never reads stderr, a success envelope on stderr is not read as one, and stderr holding an envelope followed by other text still falls through to the exit code. This corrects the 0.1.0 note that the CLI's details are carried only when it printed them on stdout. - A scripted watch can fail after its lines.
HEYFixtureClientgainedscript(watchFixture:thenFailing:),script(watchStandardOutput:thenFailing:)and their twinsscriptRepeating(watchFixture:thenFailing:)andscriptRepeating(watchStandardOutput:thenFailing:). Each stages a watch that yields its lines exactly as a plain scripted watch and then throws the given error from the stream, which is where the live client reports a watch that fell behind, a line too large and too much output. They are watch only, like the login spellings, and have no held form, since a hold only delays the call that opens the stream. This corrects the 0.4.0 note that an app stageswatchFellBehindby scripting a failing watch: that throws from the call that opens the watch, before any stream exists, so an app stages it withscript(watchFixture:thenFailing:)or its raw bytes twin instead. - A fake credential instant. The signed in auth status fixture's
expires_atnow reads2026-01-01T00:00:00Z, and the package's scrub check fails on anyexpires_atin a committed fixture holding another instant. Only a test that asserts the expiry decoded fromauth-status.jsonhas to change. Other capture timestamps stay as captured on purpose, as the fixtures README explains. - Public documents. CONTRIBUTING and SECURITY are added at the root and linked from the README, and the README's
--jsondesign rule now names the login exception. ADR 0007 records that identifiers are American, prose is British, and Swift's own spellings such ascancelledwin where they differ. ADR 0008 records that a published tag is never moved or deleted, and that a wrong release gets a patch version instead. ADR 0006 now carries the measurements behind every ceiling, ADR 0004 names the sign in progress as a third file holding a mutex, and the glossary defines a private capture.