/** * Decision cards — Block Kit rendering and answer parsing * (specs/2026-10-01-decision-cards.md §3). Pure. * * @module services/decisions/decision-card */ import type { SlackBlock } from '../../types/slack.types.js'; import type { DecisionAnswerFile, DecisionButtonValue, DecisionChoice, DecisionLanguage, DecisionOption, OwnerDecision } from '../../types/decision.types.js'; import type { SlackFile } from '../../types/slack.types.js'; /** * Short local time for the card: "14:05" today, "tomorrow 12:00", else "Thu Oct 2 12:00" * (Chinese for a Chinese owner: "明天 12:00"). * * @param at - Moment * @param now - Clock * @param lang - Owner language (absent = English) * @returns Text */ export declare function formatWhen(at: Date, now?: Date, lang?: DecisionLanguage): string; /** * The label of the default, as the card says it ("wait" for none). * * @param d - Decision * @returns Option label, or "wait" */ export declare function defaultLabel(d: Pick): string; /** * The label of an option key. * * @param d - Decision * @param key - Option key * @returns Label (the key itself when unknown) */ export declare function optionLabel(d: Pick, key: string | undefined): string; /** * The card header: a system decision's own title, else the question itself * (crewly#1367: the card leads with the question in plain words — never a * ticket id or a ticket title the owner has to decode). * * @param d - Decision * @returns Header text (≤ 150 characters, Slack's header limit) */ export declare function cardHeader(d: Pick): string; /** * Whether the card shows the question again under the header: only when the * header is something else (a system title) or had to be clipped. * * @param d - Decision * @returns True when a question section is needed */ export declare function showsQuestionSection(d: Pick): boolean; /** * The thread root for a ticket that has no thread yet. * * @param ticket - Ticket id + title * @returns mrkdwn line */ export declare function ticketThreadRootText(ticket: { id: string; title: string; }): string; /** * Button value JSON. * * @param decisionId - Decision id * @param option - Option key, `remind` or `skip` * @param instanceId - This instance (Cloud routes the click by it) * @returns JSON string */ export declare function buttonValue(decisionId: string, option: string, instanceId: string): string; /** * Parse a button value. * * @param raw - `value` of the clicked button * @returns The value, or null when it is not a decision button */ export declare function parseButtonValue(raw: unknown): DecisionButtonValue | null; /** * The context line of a pending card. * * @param d - Decision * @param now - Clock * @returns mrkdwn text */ export declare function pendingContextLine(d: OwnerDecision, now?: Date): string; /** * Blocks of an open card. * * @param d - Decision * @param instanceId - This instance id (button values) * @param now - Clock * @returns Blocks */ export declare function renderOpenCard(d: OwnerDecision, instanceId: string, now?: Date): SlackBlock[]; /** * Whether "Remind me tomorrow" is offered: not for held browser actions * (a deadline of hours), nor for system decisions (answered with exactly * their options). * * @param d - Decision * @returns True when snoozing is allowed */ export declare function canRemind(d: Pick & Partial>): boolean; /** * Whether a card is a notice: the change already happened and its one * action undoes it (model-tier auto mode, crewly#1173). Nothing to snooze * or skip — not answering keeps it. * * @param d - Decision * @returns True for a notice */ export declare function isNoticeCard(d: Partial>): boolean; /** * Whether the card offers "Skip": every card except sensitive asks, system * decisions, held browser actions and merge cards with a "not now" — those * already have a "No", which is what skipping them means ({@link skipChoice}). * A merge card's way out is named by its outcome (先不合 / Not now), not * "Skip" (crewly#1367). * * @param d - Decision * @returns True when the Skip button is shown */ export declare function canSkip(d: Pick & Partial>): boolean; /** * What "skip" means for a card: a real skip where {@link canSkip}; a * notice's default (keep what happened); else the safe way out — its "no" option, else its default when that never lets * anything through, else a real skip (dropping a sensitive ask does nothing). * * @param d - Decision * @returns Choice */ export declare function skipChoice(d: Pick & Partial>): DecisionChoice; /** * Whether the default never lets anything through (a held browser action's * "No", a declining system decision), so it is applied at the deadline even * when the decision is sensitive. * * @param d - Decision * @returns True when the default is safe to apply unanswered */ export declare function defaultIsSafe(d: Pick): boolean; /** * Blocks of a settled card (no buttons). * * @param d - Decision (resolved / defaulted / parked / cancelled) * @param ownerName - How to name the owner ("Steve"), when known * @param now - Clock * @returns Blocks */ export declare function renderSettledCard(d: OwnerDecision, ownerName?: string, now?: Date, askerName?: string): SlackBlock[]; /** * The one line a settled card shows. * * @param d - Decision * @param ownerName - Owner display name * @param now - Clock * @returns Text */ export declare function settledLine(d: OwnerDecision, ownerName?: string, now?: Date, askerName?: string): string; /** * The deadline line of a default that is applied: who does what. * "No answer by 12:00, so Owen will go with "Hold"." (settled card: "… went with …"). * A `wait` default is never announced to the owner * (specs/2026-10-02-decision-card-thread-answers.md §2); its line only * names the state, for the dashboard. * * @param d - Decision * @param now - Clock * @param askerName - The asking agent's name ("Owen"); "I" when unknown * @param settled - Past tense (the settled card) * @returns Text */ export declare function deadlineDefaultLine(d: OwnerDecision, now?: Date, askerName?: string, settled?: boolean): string; /** * The one reminder of the `wait` cards nobody answered in a thread: ONE * short line pointing at the card above, never the card repeated * (crewly#1367; specs/2026-10-02-decision-card-thread-answers.md §2). Several * cards in one thread share one reminder that counts them. * * @param ds - The thread's due cards (one or more) * @param ownerUserId - The owner's Slack id (mentioned), when known * @returns mrkdwn text */ export declare function waitReminderLine(ds: ReminderCard | ReadonlyArray, ownerUserId?: string | null): string; /** What a reminder line needs of a card. */ export type ReminderCard = Pick & Partial>; /** * The "Remind me tomorrow" reminder: one short line pointing at the card. * * @param d - The card * @param ownerUserId - Owner's Slack id (mentioned), when known * @returns Text */ export declare function remindNowLine(d: ReminderCard, ownerUserId?: string | null): string; /** * The one re-ask of a sensitive card: one short line pointing at the card. * * @param d - The card * @param ownerUserId - Owner's Slack id (mentioned), when known * @returns Text */ export declare function sensitiveReaskLine(d: ReminderCard & Pick, ownerUserId?: string | null): string; /** * How a withdrawn card names why it was closed ("ticket done", * "replaced by D-7", or the asker's own words, clipped). * * @param reason - The `cancelWhere` note * @returns Reason text (never empty) */ export declare function closedReasonLabel(reason: string | undefined): string; /** * The plain-text fallback of a card (notifications, screen readers). * * @param d - Decision * @returns Text */ export declare function cardFallbackText(d: OwnerDecision): string; /** * The option that reads as "no", when one does. * * @param options - Options * @returns Option, or null */ export declare function noOption(options: DecisionOption[]): DecisionOption | null; /** * The option ✅ / "yes" means: `yesKey` when set, else the default, else the first option. * * @param d - Decision * @returns Option key */ export declare function acceptKey(d: Pick): string; /** * Map a reaction on a card to a choice: ✅ accept, ❌ the "no" option, * ⏰ remind, 🚫 / ⏭️ skip ({@link skipChoice}). * * @param d - Decision * @param reaction - Emoji name (no colons; skin tone suffix allowed) * @returns Choice, or null when the reaction means nothing */ export declare function choiceFromReaction(d: Pick & Partial>, reaction: string): DecisionChoice | null; /** * Map a free-text thread reply to a choice: an option (label, key, number), * a skip word ("skip", 「不用了」, 「算了」, 「不管了」 — {@link skipChoice}), * a yes word (the default / first option), a no word (the "no" option), a * remind word — else the text itself, for the asker to read. * * @param d - Decision * @param text - The owner's reply * @returns Choice, or null for an empty reply */ export declare function choiceFromText(d: Pick & Partial>, text: string): DecisionChoice | null; /** * Whether a reply (lower-cased, trailing punctuation stripped) means "skip". * * @param norm - Normalised reply * @returns True for a skip word */ export declare function isSkipWord(norm: string): boolean; /** * Whether Slack ts `a` is strictly later than `b` ("1790901042.417179" vs * "1790899545.203529"). Compared as whole seconds, then the fraction, so * float precision never decides. Unreadable values are not later. * * @param a - Slack ts * @param b - Slack ts * @returns True when `a` is after `b` */ export declare function slackTsAfter(a: string | undefined, b: string | undefined): boolean; /** * The files of an owner message, as a decision answer: name, type, link and * Slack's own transcript of a recorded clip when it sent a finished one. * * @param files - Raw Slack file objects of the message * @returns Answer files (empty for none) */ export declare function answerFilesOf(files: ReadonlyArray> | undefined): DecisionAnswerFile[]; /** * What an owner's file answer is, in words for the asker: "a voice message", * "an image", "2 files". * * @param files - Answer files * @returns Phrase */ export declare function describeAnswerFiles(files: readonly DecisionAnswerFile[]): string; //# sourceMappingURL=decision-card.d.ts.map