diff --git a/skills/hey/SKILL.md b/skills/hey/SKILL.md index 50ea2aad..cdf16b7b 100644 --- a/skills/hey/SKILL.md +++ b/skills/hey/SKILL.md @@ -346,7 +346,7 @@ hey box view imbox --page next-cursor --json # Continue from an earlier listing Box names: `imbox`, `feedbox`, `trailbox`, `asidebox`, `laterbox`, `bubblebox` -**Response format:** `hey box view --json` returns the box itself — `id`, `kind`, `name`, `app_url`, `next_history_url`, `next_page` — with a `postings` array of the email threads in it. Each posting has: `id` (box item ID), `topic_id` (thread ID), `name` (subject), `seen` (read status), `created_at`, `contacts`, `summary`, `app_url`, `visible_entry_count`. Use `id` for `hey seen`, `hey unseen`, `hey move`, `hey label add`, `hey label remove`, `hey trash`, `hey spam`, `hey ignore`, and `hey stop-ignoring`, and `topic_id` for `hey thread read`, `hey reply`, `hey forward`, `hey share` and `hey attachment list`. A box item `id` passed to `hey thread read` answers `not_found`, and so does a `topic_id` passed to `hey move`. Restore is different because a trashed thread has no box item ID; use its `topic_id` from `hey search --in trash` today, or from `hey thread list --in trash` once that listing is available. +**Response format:** `hey box view --json` returns the box itself — `id`, `kind`, `name`, `app_url`, `next_history_url`, `next_page` — with a `postings` array of the email threads in it. Each posting has: `id` (box item ID), `topic_id` (thread ID), `name` (subject), `seen` (read status), `created_at`, `contacts`, `summary`, `app_url`, `visible_entry_count`. Use `id` for `hey seen`, `hey unseen`, `hey move`, `hey label add`, `hey label remove`, `hey trash`, `hey spam`, `hey ignore`, and `hey stop-ignoring`, and `topic_id` for `hey thread read`, `hey reply`, `hey forward`, `hey share` and `hey attachment list`. A box item `id` passed to `hey thread read` answers `not_found`. The reverse is not caught yet: `hey seen`, `hey unseen` and `hey move` silently ignore any id that is not one of your box items — a `topic_id`, a typo — and still answer success counting every id given, because HEY's endpoints do not report a non-match. Every id in the same call that *is* one of your box items is changed, so a mixed batch is a partial success reported as a whole one, and a `topic_id` that happens to equal one of your other box item ids marks or moves that unrelated thread. Confirm rather than trust the envelope: `hey box view --json --all --jq '{notice, next_page: .data.next_page, match: [.data.postings[] | select(.id == ) | {id, topic_id, seen}]}'` for a mark, or the destination box for a move. `--all` reads every page up to the command's cap of 101; an empty `match` with `next_page` still set means the box is larger than that, so continue with `--page ` rather than calling it a non-match, and `topic_id` in the match says which thread an id actually named. `hey trash`, `hey spam` and the label commands do answer `not_found`. A posting that bundles a contact's mail into one row can **omit `topic_id`**: a bundle names its sender rather than a thread, and its `name` joins the bundled subjects with `•`. A bundle that does carry a `topic_id` opens as that thread — its one unseen thread — and `hey threads` reads it as usual. For a bundle without one, never substitute the box item `id` (`hey threads ` answers `not_found`); there is no command that lists the threads inside a bundle, so run `hey contacts unbundle ` — the contact is in the posting's `contacts` — to list that sender's mail as separate rows, or direct the user to open the bundle in HEY. @@ -450,7 +450,7 @@ on an entry; use `hey reply`, which works the addressing out itself. `hey share` returns a URL that shows the entire thread and future emails or replies sent to it. Anyone with the link can open it. `hey unshare` turns off the sharing link. -**ID note:** Every email thread has two IDs: an `id` (its box item ID) and a `topic_id` (its thread ID). `hey seen`, `hey unseen`, `hey move`, `hey label add`, `hey label remove`, `hey trash`, `hey spam`, `hey ignore`, and `hey stop-ignoring` expect `id`. `hey thread read`, `hey share`, `hey unshare`, `hey attachment list`, `hey reply`, `hey forward`, `hey collection add`, `hey collection remove`, and `hey restore` expect `topic_id`. Passing the wrong one answers `not_found`, not a redirect. +**ID note:** Every email thread has two IDs: an `id` (its box item ID) and a `topic_id` (its thread ID). `hey seen`, `hey unseen`, `hey move`, `hey label add`, `hey label remove`, `hey trash`, `hey spam`, `hey ignore`, and `hey stop-ignoring` expect `id`. `hey thread read`, `hey share`, `hey unshare`, `hey attachment list`, `hey reply`, `hey forward`, `hey collection add`, and `hey collection remove` expect `topic_id`. Passing the wrong one answers `not_found`, not a redirect — except `hey seen`, `hey unseen` and `hey move`, which ignore an unmatched id, act on every id that does match, and answer success either way (confirm with `hey box view --json --all`, as in the note above). `hey box view --json`, `hey label view --json`, `hey collection view --json` and `hey search --json` all carry both — except a bundle posting, which can omit `topic_id` (see the Boxes section). diff --git a/tests/smoke/restore_test.go b/tests/smoke/restore_test.go index 0b60cbfd..8ea5aec5 100644 --- a/tests/smoke/restore_test.go +++ b/tests/smoke/restore_test.go @@ -82,13 +82,17 @@ func cleanupDisposableRestoreTopic(t *testing.T, posting disposableRestorePostin func TestRestore(t *testing.T) { first := createDisposableRestoreTopic(t, "restore test") - second := createDisposableRestoreTopic(t, "bulk restore test") t.Cleanup(func() { cleanupDisposableRestoreTopic(t, first) }) + second := createDisposableRestoreTopic(t, "bulk restore test") t.Cleanup(func() { cleanupDisposableRestoreTopic(t, second) }) heyOK(t, "trash", strconv.FormatInt(first.ID, 10), strconv.FormatInt(second.ID, 10), "--json") firstTopicID := waitForTrashedTopicID(t, first.Name) secondTopicID := waitForTrashedTopicID(t, second.Name) + if firstTopicID != first.TopicID || secondTopicID != second.TopicID { + t.Fatalf("Trash search returned topic IDs %d and %d, want disposable threads %d and %d", + firstTopicID, secondTopicID, first.TopicID, second.TopicID) + } stdout := heyOK(t, "restore", strconv.FormatInt(firstTopicID, 10), strconv.FormatInt(secondTopicID, 10), "--json") var response Response