Skip to content

Commit 25ab7b5

Browse files
committed
Events from the Brisshell update
1 parent 4b187f0 commit 25ab7b5

7 files changed

Lines changed: 300 additions & 98 deletions

File tree

.agents/skills/import-checklist-event/SKILL.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Convert official notices into validated `ChecklistEvent` records without inferri
1414
3. Extract and keep distinct:
1515
- notice title;
1616
- separately trackable event title;
17+
- the exact visible notice heading for each separately trackable event;
1718
- exact start and end instants in UTC;
1819
- participation channel, including Discord when published;
1920
- each recurring participation or claim window.
@@ -24,6 +25,10 @@ Convert official notices into validated `ChecklistEvent` records without inferri
2425
- Use `daily` only for published daily participation or claim windows and `weekly` only when explicitly published. Otherwise use `none`.
2526
- Omit `recurrenceStartAt` for the default `00:00 UTC` daily boundary. Set it to a full UTC ISO instant when the published recurring boundary differs. Preserve the event's separately published `startAt` and `endAt`.
2627
- Preserve an existing record's `id` when updating it.
28+
- When the official notice URL is known, set `noticeUrl` to that URL with a
29+
`#:~:text=` fragment containing the percent-encoded exact visible event heading
30+
so the event-name link scrolls to its section on anchorless notice pages. Use
31+
the plain notice URL only when the page has no trustworthy exact heading.
2732
- For a new record, derive a stable unique ID from the official URL identifier when available; otherwise slugify the event title. Check uniqueness in the complete dataset.
2833
- Keep one record per separately completable checklist concern. Do not split a notice solely because it has multiple prose sections.
2934
- Do not add an event whose `endAt` is less than or equal to the current UTC instant unless the user explicitly requests historical retention.

PLANS.md

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ Implementation is intentionally deferred. Keep every item unchecked until the co
110110
- [x] Add the 100-Day Anniversary Check-In Pass and Bonus Time daily event schedules using their published UTC periods.
111111
- [x] Add the First Summer Dive event set and Cool Summer Vacation login reward using their published UTC periods and explicit daily-reset behavior.
112112
- [x] Retire the July 28 event set and add the Mabel and Discord events using the completed maintenance time and published UTC periods.
113-
- [x] Import official checklist notices with the project skill, preserve published non-midnight reset boundaries, and remove expired limited events during each import without changing completion records.
113+
- [x] Import official checklist notices, including the Brisshell event set with official source links and verified metadata, using the project skill; preserve published non-midnight reset boundaries; and remove expired limited events during each import without changing completion records.
114114
- [x] Show Event alongside Daily or Weekly badges when an event also has a recurring reset, and show expired rows by default with reduced opacity and struck-through names.
115115
- [x] Distinguish event rows with daily teal, weekly violet, one-time fuchsia, and Discord blue gradients; align their badges while reserving amber row styling for ending-soon status and destructive styling for overdue items.
116116
- [x] Show player-created Task and Event notes beneath item names with a compact two-line limit.
@@ -129,14 +129,7 @@ Implementation is intentionally deferred. Keep every item unchecked until the co
129129

130130
## Chunk 2: Artifacts Inventory
131131

132-
<<<<<<< HEAD
133-
<<<<<<< HEAD
134-
135-
- [x] # Add Brisshell's Tier 5 Earth Assassin artifact, Monstrous Longing.
136-
- [x] Add Brisshell's Tier 5 Earth Assassin artifact with a temporary title pending its official name.
137-
> > > > > > > # 59afb26 (Prepare Brishell assets)
138-
- [x] Add Brisshell's Tier 5 Earth Assassin artifact with a temporary title pending its official name.
139-
> > > > > > > 59afb26 (Prepare Brishell assets)
132+
- [x] Add Brisshell's Tier 5 Earth Assassin artifact, Monstrous Longing.
140133
- [x] Define typed artifact data, owned-artifact fields, images, and validation from the game source.
141134
- [x] Add owned-artifact create, edit, delete, reset, local persistence, and mutation timestamps.
142135
- [x] Build the Artifacts page with cards, search, filters, empty states, and accessible forms.

src/components/checklist/components/checklist-item-row.tsx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -163,19 +163,39 @@ export const ChecklistItemRow = ({
163163
<div className="mr-2 flex min-w-0 flex-1 items-center gap-1.5 leading-tight">
164164
<ChecklistBadges definition={definition} />
165165
<div className="min-w-0 flex-1">
166-
<span
167-
className={cn(
168-
"line-clamp-2 font-semibold",
169-
customTask
170-
? "text-sm sm:text-base"
171-
: "text-xs sm:text-sm md:text-base",
172-
(status === CHECKLIST_STATUSES.COMPLETED ||
173-
status === CHECKLIST_STATUSES.EXPIRED) &&
174-
"line-through",
175-
)}
176-
>
177-
{definition.title}
178-
</span>
166+
{definition.noticeUrl ? (
167+
<a
168+
className={cn(
169+
"line-clamp-2 w-fit max-w-full font-semibold underline-offset-4 hover:text-primary focus-visible:outline-none",
170+
customTask
171+
? "text-sm sm:text-base"
172+
: "text-xs sm:text-sm md:text-base",
173+
status === CHECKLIST_STATUSES.COMPLETED ||
174+
status === CHECKLIST_STATUSES.EXPIRED
175+
? "line-through hover:[text-decoration-line:underline_line-through] focus-visible:[text-decoration-line:underline_line-through]"
176+
: "hover:underline focus-visible:underline",
177+
)}
178+
href={definition.noticeUrl}
179+
target="_blank"
180+
rel="noreferrer"
181+
>
182+
{definition.title}
183+
</a>
184+
) : (
185+
<span
186+
className={cn(
187+
"line-clamp-2 font-semibold",
188+
customTask
189+
? "text-sm sm:text-base"
190+
: "text-xs sm:text-sm md:text-base",
191+
(status === CHECKLIST_STATUSES.COMPLETED ||
192+
status === CHECKLIST_STATUSES.EXPIRED) &&
193+
"line-through",
194+
)}
195+
>
196+
{definition.title}
197+
</span>
198+
)}
179199
{item.notes && (
180200
<p className="mt-0.5 line-clamp-2 break-words whitespace-pre-line text-xs font-normal text-muted-foreground">
181201
{item.notes}

src/components/checklist/components/checklist-page.test.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ const { permanentEvents, eventsData } = vi.hoisted(() => ({
6565
{
6666
id: "fixture-gulgak",
6767
title: "Fixture Gulgak",
68+
noticeUrl:
69+
"https://forum.netmarble.com/stardive_gl/view/6/548#:~:text=Fixture%20Gulgak",
6870
kind: "event",
6971
startAt: "2026-07-15T00:00:00.000Z",
7072
endAt: "2026-07-28T23:59:00.000Z",
@@ -262,6 +264,18 @@ describe("ChecklistPage", () => {
262264
expect(screen.queryByText("Fixture Rift")).toBeNull();
263265
});
264266

267+
it("links official event notices and leaves unlinked titles plain", () => {
268+
render(<ChecklistPage />);
269+
270+
const linkedTitle = screen.getByRole("link", { name: "Fixture Gulgak" });
271+
expect(linkedTitle.getAttribute("href")).toBe(
272+
"https://forum.netmarble.com/stardive_gl/view/6/548#:~:text=Fixture%20Gulgak",
273+
);
274+
expect(linkedTitle.getAttribute("target")).toBe("_blank");
275+
expect(linkedTitle.getAttribute("rel")).toBe("noreferrer");
276+
expect(screen.getByText("Fixture Ice").tagName).toBe("SPAN");
277+
});
278+
265279
it("fully completes an event without losing its occurrence completion", () => {
266280
render(<ChecklistPage />);
267281

src/data/checklist/CHECKLIST_DATA.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export type ChecklistDefinition = {
4545
title: string;
4646
description?: string;
4747
noticeTitle?: string;
48+
noticeUrl?: string;
4849
kind: ChecklistKind;
4950
startAt: string;
5051
/**

src/data/events/EVENTS_DATA.test.ts

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,176 @@ describe("EVENTS_DATA", () => {
77
expect.arrayContaining([
88
"100-day-launch-anniversary-check-in",
99
"20260807-CAT-DAY",
10+
"monsterling-trait-change-support",
11+
"legendary-monster-reginula-power-up-support",
12+
"inquisitors-day-off-event-stage-inquisition",
13+
"arbiters-divine-indulgence",
14+
"inquisitor-mabel-7-day-gifts",
15+
"special-missions-with-mabel",
16+
"mabel-invitation-to-break-the-ice",
17+
"anomaly-blue-shadow",
18+
"mabel-character-trivia-discord",
19+
"forum.netmarble.com/stardive_gl/view/6/521-10-day-check-in",
20+
"forum.netmarble.com/stardive_gl/view/6/531-bonus-time-event",
1021
]),
1122
);
23+
expect(EVENTS_DATA).toHaveLength(11);
24+
expect(EVENTS_DATA.map(({ id }) => id)).toContain(
25+
"inquisitors-day-off-shop-story-missions",
26+
);
27+
});
28+
29+
it("imports the Brisshell notice periods and published recurrence metadata", () => {
30+
const brisshellEvents = EVENTS_DATA.filter(
31+
({ id }) => id !== "inquisitors-day-off-shop-story-missions",
32+
);
33+
expect(brisshellEvents).toHaveLength(10);
34+
expect(brisshellEvents.filter((event) => event.noticeUrl)).toHaveLength(10);
35+
expect(
36+
brisshellEvents.filter((event) =>
37+
event.noticeUrl?.includes("/548#:~:text="),
38+
),
39+
).toHaveLength(9);
40+
expect(
41+
brisshellEvents.filter((event) =>
42+
event.noticeUrl?.includes("/556#:~:text="),
43+
),
44+
).toHaveLength(1);
45+
const expectedNoticeUrls = {
46+
"tons-of-recruitment-tickets-check-in-streak-gift":
47+
"https://forum.netmarble.com/stardive_gl/view/6/548#:~:text=Tons%20of%20Recruitment%20Tickets!%20Check-In%20Streak%20Gift",
48+
"girl-from-the-void-event-stage-brisshells-link-rush":
49+
"https://forum.netmarble.com/stardive_gl/view/6/548#:~:text=Girl%20from%20the%20Void%20%E2%80%94%20Event%20Stage%2FBrisshell%E2%80%99s%20Link%20Rush",
50+
"girl-from-the-void-shop-story-missions":
51+
"https://forum.netmarble.com/stardive_gl/view/6/548#:~:text=Girl%20from%20the%20Void%20%E2%80%94%20Shop%2FStory%2FMissions",
52+
"th-this-is-for-being-my-friend":
53+
"https://forum.netmarble.com/stardive_gl/view/6/548#:~:text=Th-this%20is%20for%20being%20my%20friend...",
54+
"brisshells-7-day-gifts":
55+
"https://forum.netmarble.com/stardive_gl/view/6/548#:~:text=Brisshell%E2%80%99s%207-Day%20Gifts",
56+
"special-missions-with-brisshell":
57+
"https://forum.netmarble.com/stardive_gl/view/6/548#:~:text=Special%20Missions%20with%20Brisshell",
58+
"equipment-crafting-mission":
59+
"https://forum.netmarble.com/stardive_gl/view/6/548#:~:text=Equipment%20Crafting%20Mission",
60+
"anomaly-el-dorado-guardian":
61+
"https://forum.netmarble.com/stardive_gl/view/6/548#:~:text=Anomaly%3A%20El%20Dorado%20Guardian",
62+
"brisshell-an-invitation-to-break-the-ice":
63+
"https://forum.netmarble.com/stardive_gl/view/6/548#:~:text=An%20Invitation%20to%20Break%20the%20Ice",
64+
"brisshell-screenshot-event-discord":
65+
"https://forum.netmarble.com/stardive_gl/view/6/556#:~:text=Brisshell%20Screenshot%20Event",
66+
} as const;
67+
for (const [id, noticeUrl] of Object.entries(expectedNoticeUrls)) {
68+
expect(EVENTS_DATA.find((event) => event.id === id)).toMatchObject({
69+
id,
70+
noticeUrl,
71+
});
72+
}
73+
expect(
74+
brisshellEvents.find(
75+
(event) => event.id === "brisshell-screenshot-event-discord",
76+
),
77+
).toMatchObject({
78+
noticeTitle:
79+
"8/19 (Wed) 「The Girl from the Void」Brisshell Screenshot Event Notice",
80+
noticeUrl:
81+
"https://forum.netmarble.com/stardive_gl/view/6/556#:~:text=Brisshell%20Screenshot%20Event",
82+
startAt: "2026-08-19T05:25:23.088Z",
83+
});
84+
expect(brisshellEvents.map(({ noticeTitle }) => noticeTitle)).toEqual([
85+
"8/18 (Tue) [Girl from the Void] Event Notice",
86+
"8/18 (Tue) [Girl from the Void] Event Notice",
87+
"8/18 (Tue) [Girl from the Void] Event Notice",
88+
"8/18 (Tue) [Girl from the Void] Event Notice",
89+
"8/18 (Tue) [Girl from the Void] Event Notice",
90+
"8/18 (Tue) [Girl from the Void] Event Notice",
91+
"8/18 (Tue) [Girl from the Void] Event Notice",
92+
"8/18 (Tue) [Girl from the Void] Event Notice",
93+
"8/18 (Tue) [Girl from the Void] Event Notice",
94+
"8/19 (Wed) 「The Girl from the Void」Brisshell Screenshot Event Notice",
95+
]);
96+
expect(
97+
EVENTS_DATA.find(
98+
(event) => event.id === "inquisitors-day-off-shop-story-missions",
99+
),
100+
).not.toHaveProperty("noticeUrl");
101+
102+
const expectedEvents = {
103+
"tons-of-recruitment-tickets-check-in-streak-gift": [
104+
"2026-08-19T05:30:00.000Z",
105+
"2026-09-02T05:30:00.000Z",
106+
"daily",
107+
],
108+
"girl-from-the-void-event-stage-brisshells-link-rush": [
109+
"2026-08-19T05:30:00.000Z",
110+
"2026-09-08T23:59:00.000Z",
111+
"none",
112+
],
113+
"girl-from-the-void-shop-story-missions": [
114+
"2026-08-19T05:30:00.000Z",
115+
"2026-09-15T23:59:00.000Z",
116+
"none",
117+
],
118+
"th-this-is-for-being-my-friend": [
119+
"2026-08-19T05:30:00.000Z",
120+
"2026-09-08T23:59:00.000Z",
121+
"none",
122+
],
123+
"brisshells-7-day-gifts": [
124+
"2026-08-19T05:30:00.000Z",
125+
"2026-09-08T23:59:00.000Z",
126+
"daily",
127+
],
128+
"special-missions-with-brisshell": [
129+
"2026-08-19T05:30:00.000Z",
130+
"2026-09-08T23:59:00.000Z",
131+
"none",
132+
],
133+
"equipment-crafting-mission": [
134+
"2026-08-19T05:30:00.000Z",
135+
"2026-08-25T23:59:00.000Z",
136+
"none",
137+
],
138+
"anomaly-el-dorado-guardian": [
139+
"2026-08-26T00:00:00.000Z",
140+
"2026-09-08T23:59:00.000Z",
141+
"daily",
142+
],
143+
"brisshell-an-invitation-to-break-the-ice": [
144+
"2026-09-02T00:00:00.000Z",
145+
"2026-09-08T23:59:00.000Z",
146+
"none",
147+
],
148+
"brisshell-screenshot-event-discord": [
149+
"2026-08-19T05:25:23.088Z",
150+
"2026-08-26T01:00:00.000Z",
151+
"none",
152+
],
153+
} as const;
154+
155+
for (const [id, [startAt, endAt, recurrence]] of Object.entries(
156+
expectedEvents,
157+
)) {
158+
expect(EVENTS_DATA.find((event) => event.id === id)).toMatchObject({
159+
id,
160+
startAt,
161+
endAt,
162+
recurrence,
163+
});
164+
}
165+
166+
expect(
167+
EVENTS_DATA.find(
168+
(event) => event.id === "brisshell-screenshot-event-discord",
169+
),
170+
).toMatchObject({ participation: "discord" });
171+
for (const id of [
172+
"tons-of-recruitment-tickets-check-in-streak-gift",
173+
"brisshells-7-day-gifts",
174+
"anomaly-el-dorado-guardian",
175+
]) {
176+
expect(EVENTS_DATA.find((event) => event.id === id)).not.toHaveProperty(
177+
"recurrenceStartAt",
178+
);
179+
}
12180
});
13181

14182
it("defines unique, valid UTC event periods and optional reset anchors", () => {

0 commit comments

Comments
 (0)