Skip to content

Releases: Soules-Studio-Ltd/HEYCliKit

HEYCliKit 0.6.0

Choose a tag to compare

@soulesidibe soulesidibe released this 23 Sep 09:41

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.topicID was TopicID and is now TopicID?. The CLI derives a posting's topic_id from its app URL and leaves the key out when it has none, and TopicID(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 through appURL. Inside a watch line nothing changes: the CLI prints no topic_id on a posting there and the line's own thread_id still 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.boxID and the Posting.boxID that forwards them were BoxID and are now BoxID?. The CLI leaves box_id out 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.bundleAppURL was URL and is now URL?, since the CLI leaves app_bundle_url out when a bundle has no address of its own. The bundle's appURL, which opens its row, is unchanged and still required.
  • A Screener entry's topic id is optional, source breaking. ScreenerEntry.topicID was TopicID and is now TopicID?, 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. Posting gained a third case, other(Posting.Other), for any kind besides topic and bundle, so an exhaustive switch over Posting no longer compiles until it handles .other or has a default. Posting.Other carries the fields every posting shares, read exactly as a single posting and a bundle read them, and kind, the CLI's own string verbatim, which is empty when the CLI printed no kind at all. The forwarding properties on Posting read 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, entry included even though hey-sdk's own schema declares it, and inside a watch it made an added or updated line unrecognized, 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. A kind that 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_contacts and visible_entry_count on a posting, name, email_address, initials and avatar_background_color on a contact, and name, email_address, subject and summary on a Screener entry now decode to "", 0 or [] 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 no addressed_contacts, a blank subject prints no name, a sender whose name has no letters prints no initials, and a sender HEY knows only by a bare address prints neither name nor summary. It mattered most on a watch: a decoding failure anywhere inside a line made the whole line unrecognized, so an added line for a Bcc only mail told an app nothing at all, and it now arrives as the change it is. app_url, active_at and observed_at stay required, and so do the id of 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 .iso8601 strategy, and under it the Foundation that macOS 15 ships refuses fractional seconds. Every observed_at and every watch line's at carries one, so on the package's minimum OS every page read failed and every watch line, ready included, was unrecognized. 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 keeps Z and a local offset such as +02:00, which is what expires_at carries. 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, Z and +02:00 included.
  • A refused row is dropped and counted. BoxPage gained refusedRowCount. A row the package cannot read at all, one with no id, app_url, active_at, observed_at or creator, 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 + refusedRowCount is 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 whose postings is absent or not a list still fails as a whole. A watch line whose posting is refused this way is still unrecognized, 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.description now 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 as Expected String at data[1].version., The key postings is missing at data., The value at data[0].status could not be read. or The 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 in rawText, which is unchanged.
  • A sign in that was not completed says why. LoginFailure gained kind, a CaseIterable LoginFailure.Kind that is timedOut, accessDenied, cancelled or notClassified, 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 is cancelled whatever 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, is notClassified. init(exitStatus:standardError:) keeps its signature and classifies the ending by the same rule, never as cancelled, since an ending alone cannot say a cancel was asked for, and init(exitStatus:standardError:kind:) is added to state any kind. HEYFixtureClient gained script(loginNotCompleted:), which scripts a sign in ending with any kind, and a scripted cancel resolves as cancelled by the same rule as a live one. This is a behaviour change: kind takes part in equality, so a test that compared a cancelled sign in against a failure built with the two argument initialiser now states kind: .cancelled. Adding a kind later is a source break for an exhaustive switch, 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. LoginFailure is now CustomStringConvertible, and its description names the kind and how the child ended, such as The sign in timed out (exit code 3)., and nothing read from stderr, so an app may log it verbatim. A LoginOutcome is 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 printed standardError in 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 own CustomStringConvertible conformance on LoginFailure, which it now removes.

HEYCliKit 0.5.0

Choose a tag to compare

@soulesidibe soulesidibe released this 23 Sep 09:41

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 now init?(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 writes NonEmptySet(elements: ids), and the variadic spelling is unchanged.
  • Cancelling a sign in is a method. LoginHandle.cancel was a public stored closure and is now cancel(). A call such as handle.cancel() compiles unchanged.
  • A sign in's outcome is a property, source breaking. LoginHandle.outcome was a stored closure awaited as await handle.outcome() and is now read as await 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.release was a public stored closure and is now release(). A call such as held.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 now WatchLine.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, error and hint. This is a behaviour change: hey 1.4.0 prints a signed out read's envelope on stderr with nothing on stdout, so HEYCliKitError.signedOut now 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. HEYFixtureClient gained script(watchFixture:thenFailing:), script(watchStandardOutput:thenFailing:) and their twins scriptRepeating(watchFixture:thenFailing:) and scriptRepeating(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 stages watchFellBehind by scripting a failing watch: that throws from the call that opens the watch, before any stream exists, so an app stages it with script(watchFixture:thenFailing:) or its raw bytes twin instead.
  • A fake credential instant. The signed in auth status fixture's expires_at now reads 2026-01-01T00:00:00Z, and the package's scrub check fails on any expires_at in a committed fixture holding another instant. Only a test that asserts the expiry decoded from auth-status.json has 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 --json design rule now names the login exception. ADR 0007 records that identifiers are American, prose is British, and Swift's own spellings such as cancelled win 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.