Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions src/domain/weeklySummary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,13 @@ export function renderEditedLine(args: {
].join("\n");
}

/** A removed Event: struck through and labeled, staying visible rather than deleted from the listing. */
/**
* A removed Event: struck through and labeled, staying visible rather than
* deleted from the listing. Never linked — the calendar event itself is
* gone, so its `calendarLink` (if any) would just 404.
*/
export function renderRemovedLine(snapshot: WeeklySummaryEventInfo): string {
return `~${formatWeeklySummaryLine(snapshot)}~ _(removed)_`;
return `~${formatWeeklySummaryLine({ ...snapshot, calendarLink: null })}~ _(removed)_`;
}

/** An Event discovered after the summary's initial post — tagged, not struck through (no "original" to contrast). */
Expand Down Expand Up @@ -228,6 +232,18 @@ export type WeeklySummaryLineEntry = {
text: string;
};

/**
* Slack mrkdwn blockquotes are per-line (`> `), so a multi-line entry (an
* edited line's struck-through/current pair) needs the marker on each of
* its lines to stay inside the same quote.
*/
function blockquoteLines(text: string): string {
return text
.split("\n")
.map((line) => `> ${line}`)
.join("\n");
}

/**
* The standard Google Calendar "add this calendar" URL for a calendar id, or
* `undefined` when there's no id (a calendar setting left unconfigured) —
Expand Down Expand Up @@ -278,5 +294,8 @@ export function assembleWeeklySummaryMessage(args: {
const sorted = [...args.entries].sort(
(a, b) => a.sortKey.getTime() - b.sortKey.getTime()
);
return [header, ...sorted.map((e) => e.text), ...footer].join("\n\n");
// Joined with a blank *quoted* line (`>`), not `\n\n`, so entries stay one
// continuous blockquote instead of splitting into a separate quote box per entry.
const list = sorted.map((e) => blockquoteLines(e.text)).join("\n>\n");
return [header, list, ...footer].join("\n\n");
}
41 changes: 41 additions & 0 deletions test/weeklySummary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,16 @@ test("a removed line strikes through the whole line and labels it removed", () =
assert.match(line, /\(removed\)/);
});

test("a removed line never links the title, even if the snapshot had a calendar link", () => {
const linked: WeeklySummaryEventInfo = {
...hourlyInfo,
calendarLink: "https://calendar.google.com/event?eid=abc123",
};
const line = renderRemovedLine(linked);
assert.doesNotMatch(line, /</);
assert.match(line, /~\*Team Meeting\*.*~/);
});

test("a new mid-week line is tagged, not struck through", () => {
const line = renderNewLine(hourlyInfo);
assert.match(line, /^🆕 _New_/);
Expand Down Expand Up @@ -320,6 +330,37 @@ test("a subscribe link appends a footer line to the assembled message", () => {
);
});

test("the entry list is rendered as a single blockquote, not a bare list", () => {
const message = assembleWeeklySummaryMessage({
weekStart: new Date("2026-01-05T00:00:00Z"),
weekEnd: new Date("2026-01-12T00:00:00Z"),
entries: [
{ sortKey: new Date("2026-01-06T00:00:00Z"), text: "First event" },
{ sortKey: new Date("2026-01-10T00:00:00Z"), text: "Second event" },
],
});
assert.match(message, /> First event\n>\n> Second event/);
// The header stays outside the quote.
assert.match(message, /^\*This Week\*/);
});

test("a multi-line entry (an edited line) gets the quote marker on every line", () => {
const edited = { ...hourlyInfo, location: "Room 310" };
const line = renderEditedLine({
snapshot: hourlyInfo,
current: edited,
changedFields: ["location"],
});
const message = assembleWeeklySummaryMessage({
weekStart: new Date("2026-01-05T00:00:00Z"),
weekEnd: new Date("2026-01-12T00:00:00Z"),
entries: [{ sortKey: hourlyInfo.startsAt, text: line }],
});
for (const l of line.split("\n")) {
assert.ok(message.includes(`> ${l}`));
}
});

test("no subscribe link means no footer line", () => {
const message = assembleWeeklySummaryMessage({
weekStart: new Date("2026-01-05T00:00:00Z"),
Expand Down
Loading