Skip to content

Commit ea77c58

Browse files
authored
Implement Incoming Jumps feature end-to-end (#185)
2 parents 0f526fe + c113d9a commit ea77c58

10 files changed

Lines changed: 4464 additions & 19 deletions

File tree

.changeset/sharp-ties-throw.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@branchforge/shared": patch
3+
"@branchforge/frontend": patch
4+
"@branchforge/backend": patch
5+
---
6+
7+
Implemented incoming jumps section to write mode

apps/backend/scripts/seed-technical-badges.ts

Lines changed: 195 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ import {
1818
labels,
1919
labelLines,
2020
characters,
21+
routeConfigs,
2122
type NewLabelLine,
2223
} from "../src/db/schema/index.js";
23-
import { eq } from "drizzle-orm";
24+
import { eq, and } from "drizzle-orm";
2425
import { calculateContentHash } from "../src/lib/hash.js";
2526
import { hashPassword } from "../src/services/auth.service.js";
27+
import type { IncomingJump } from "@branchforge/shared";
2628

2729
const TEST_EMAIL = "tech-badges-test@example.com";
2830
const TEST_PASSWORD = "testpassword123";
@@ -139,6 +141,33 @@ async function seedTechnicalBadgesData() {
139141
console.log("✅ Using existing test project file");
140142
}
141143

144+
// Create route config for outgoing jump display
145+
const TEST_ROUTE_KEY = "tech_test";
146+
const [existingRouteConfig] = await db
147+
.select()
148+
.from(routeConfigs)
149+
.where(
150+
and(
151+
eq(routeConfigs.projectId, testProject.id),
152+
eq(routeConfigs.routeKey, TEST_ROUTE_KEY)
153+
)
154+
)
155+
.limit(1);
156+
157+
if (!existingRouteConfig) {
158+
await db.insert(routeConfigs).values({
159+
projectId: testProject.id,
160+
routeKey: TEST_ROUTE_KEY,
161+
routeName: "Tech Test Route",
162+
jumpPrefix: "tech_test_",
163+
sortOrder: 0,
164+
isShared: false,
165+
});
166+
console.log(`✅ Created route config: ${TEST_ROUTE_KEY}`);
167+
} else {
168+
console.log(`✅ Using existing route config: ${TEST_ROUTE_KEY}`);
169+
}
170+
142171
// Clean up existing test label
143172
const [existingLabel] = await db
144173
.select()
@@ -160,6 +189,7 @@ async function seedTechnicalBadgesData() {
160189
projectFileId: projectFileId!,
161190
title: TEST_LABEL_TITLE,
162191
labelNumber: 1,
192+
route: TEST_ROUTE_KEY,
163193
sequenceOrder: 0,
164194
visibility: "EXCLUSIVE",
165195
status: "DRAFT",
@@ -574,6 +604,168 @@ async function seedTechnicalBadgesData() {
574604
await db.insert(labelLines).values(testLines);
575605
console.log(`✅ Created ${testLines.length} test lines\n`);
576606

607+
// Build incoming jumps for target labels
608+
const targetLabelMap = new Map(
609+
createdTargetLabels.map((l) => [l.labelName, l.id])
610+
);
611+
612+
const incomingJumpsByTarget: Record<string, IncomingJump[]> = {
613+
label_fight: [
614+
{
615+
sourceLabelId: labelId,
616+
sourceLabelTitle: TEST_LABEL_TITLE,
617+
sourceLabelName: TEST_LABEL_TITLE,
618+
jumpType: "MENU_CHOICE",
619+
choiceText: "Fight",
620+
},
621+
{
622+
sourceLabelId: labelId,
623+
sourceLabelTitle: TEST_LABEL_TITLE,
624+
sourceLabelName: TEST_LABEL_TITLE,
625+
jumpType: "MENU_CHOICE",
626+
choiceText: "The Kingdom",
627+
},
628+
],
629+
label_run: [
630+
{
631+
sourceLabelId: labelId,
632+
sourceLabelTitle: TEST_LABEL_TITLE,
633+
sourceLabelName: TEST_LABEL_TITLE,
634+
jumpType: "MENU_CHOICE",
635+
choiceText: "Run away",
636+
conditions: {
637+
variables: ["has_stamina"],
638+
},
639+
},
640+
{
641+
sourceLabelId: labelId,
642+
sourceLabelTitle: TEST_LABEL_TITLE,
643+
sourceLabelName: TEST_LABEL_TITLE,
644+
jumpType: "MENU_CHOICE",
645+
choiceText: "The Rebels",
646+
},
647+
],
648+
label_talk: [
649+
{
650+
sourceLabelId: labelId,
651+
sourceLabelTitle: TEST_LABEL_TITLE,
652+
sourceLabelName: TEST_LABEL_TITLE,
653+
jumpType: "MENU_CHOICE",
654+
choiceText: "Talk",
655+
},
656+
{
657+
sourceLabelId: labelId,
658+
sourceLabelTitle: TEST_LABEL_TITLE,
659+
sourceLabelName: TEST_LABEL_TITLE,
660+
jumpType: "MENU_CHOICE",
661+
choiceText: "Stay Neutral",
662+
},
663+
],
664+
label_light: [
665+
{
666+
sourceLabelId: labelId,
667+
sourceLabelTitle: TEST_LABEL_TITLE,
668+
sourceLabelName: TEST_LABEL_TITLE,
669+
jumpType: "MENU_CHOICE",
670+
choiceText: "Light path",
671+
},
672+
],
673+
label_dark: [
674+
{
675+
sourceLabelId: labelId,
676+
sourceLabelTitle: TEST_LABEL_TITLE,
677+
sourceLabelName: TEST_LABEL_TITLE,
678+
jumpType: "MENU_CHOICE",
679+
choiceText: "Dark path",
680+
},
681+
],
682+
label_gold: [
683+
{
684+
sourceLabelId: labelId,
685+
sourceLabelTitle: TEST_LABEL_TITLE,
686+
sourceLabelName: TEST_LABEL_TITLE,
687+
jumpType: "MENU_CHOICE",
688+
choiceText: "Gold coins",
689+
},
690+
{
691+
sourceLabelId: labelId,
692+
sourceLabelTitle: TEST_LABEL_TITLE,
693+
sourceLabelName: TEST_LABEL_TITLE,
694+
jumpType: "MENU_CHOICE",
695+
choiceText: "Aggressive approach",
696+
},
697+
],
698+
label_gem: [
699+
{
700+
sourceLabelId: labelId,
701+
sourceLabelTitle: TEST_LABEL_TITLE,
702+
sourceLabelName: TEST_LABEL_TITLE,
703+
jumpType: "MENU_CHOICE",
704+
choiceText: "Magic gem",
705+
conditions: {
706+
variables: ["has_gem"],
707+
},
708+
},
709+
{
710+
sourceLabelId: labelId,
711+
sourceLabelTitle: TEST_LABEL_TITLE,
712+
sourceLabelName: TEST_LABEL_TITLE,
713+
jumpType: "MENU_CHOICE",
714+
choiceText: "Stealthy approach",
715+
},
716+
],
717+
label_nothing: [
718+
{
719+
sourceLabelId: labelId,
720+
sourceLabelTitle: TEST_LABEL_TITLE,
721+
sourceLabelName: TEST_LABEL_TITLE,
722+
jumpType: "MENU_CHOICE",
723+
choiceText: "Nothing",
724+
},
725+
{
726+
sourceLabelId: labelId,
727+
sourceLabelTitle: TEST_LABEL_TITLE,
728+
sourceLabelName: TEST_LABEL_TITLE,
729+
jumpType: "MENU_CHOICE",
730+
choiceText: "Diplomatic approach",
731+
},
732+
],
733+
chapter_two_scene_one: [
734+
{
735+
sourceLabelId: labelId,
736+
sourceLabelTitle: TEST_LABEL_TITLE,
737+
sourceLabelName: TEST_LABEL_TITLE,
738+
jumpType: "AUTOMATIC",
739+
choiceText: "Automatic jump",
740+
},
741+
...createdTargetLabels
742+
.filter((l) =>
743+
["label_gold", "label_gem", "label_nothing"].includes(l.labelName)
744+
)
745+
.map((l) => ({
746+
sourceLabelId: l.id,
747+
sourceLabelTitle: targetLabels.find(
748+
(t) => t.labelName === l.labelName
749+
)!.name,
750+
sourceLabelName: l.labelName,
751+
jumpType: "AUTOMATIC" as const,
752+
choiceText: "Automatic jump" as const,
753+
})),
754+
],
755+
};
756+
757+
for (const [labelName, jumps] of Object.entries(incomingJumpsByTarget)) {
758+
const targetId = targetLabelMap.get(labelName);
759+
if (targetId) {
760+
await db
761+
.update(labels)
762+
.set({ incomingJumps: jumps })
763+
.where(eq(labels.id, targetId));
764+
console.log(`✅ Added ${jumps.length} incoming jump(s) to ${labelName}`);
765+
}
766+
}
767+
console.log();
768+
577769
// Summary
578770
console.log("🎉 Technical badges test data seeded successfully!\n");
579771
console.log("Test features included:");
@@ -586,6 +778,8 @@ async function seedTechnicalBadgesData() {
586778
console.log(" ✅ Variables conditions");
587779
console.log(" ✅ Visual statements (SCENE, SHOW, HIDE)");
588780
console.log(" ✅ Jump targets");
781+
console.log(" ✅ Incoming jumps on target labels");
782+
console.log(" ✅ Outgoing jump (route-based prefix via route config)");
589783
console.log(" ✅ Combinations of multiple features");
590784
console.log(
591785
` ✅ ${createdTargetLabels.length} target labels created for testing resolution\n`
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE "labels" ADD COLUMN "incoming_jumps" jsonb;

0 commit comments

Comments
 (0)