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
6 changes: 4 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,10 @@ export type Construction = {
)[];
pre_note?: string;

pre_terrain?: string; // if starts with f_, then furniture_id, else terrain_id
post_terrain?: string; // as above
pre_terrain?: string;
pre_furniture?: string;
post_terrain?: string;
post_furniture?: string;

pre_flags?: string | string[];
post_flags?: string[];
Expand Down
53 changes: 32 additions & 21 deletions src/types/Construction.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import JsonView from "../JsonView.svelte";
import { getContext, untrack } from "svelte";
import { CBNData } from "../data";
import type { Construction, RequirementData } from "../types";
import {
getConstructionPrerequisites,
getConstructionResults,
isNullConstructionResult,
} from "./construction";
import ItemLink from "./ItemLink.svelte";
import RequirementDataTools from "./item/RequirementDataTools.svelte";
import { i18n, gameSingular, gameSingularName } from "../utils/i18n";
Expand Down Expand Up @@ -42,6 +47,8 @@ const components = requirements.flatMap(([req, count]) => {
const byproducts = data.flattenItemGroup(
data.normalizeItemGroup(construction.byproducts, "collection"),
);
const prerequisites = getConstructionPrerequisites(construction);
const results = getConstructionResults(construction);

const preFlags: { flag: string; force_terrain?: boolean }[] = [];
if (construction.pre_flags)
Expand Down Expand Up @@ -71,14 +78,16 @@ if (construction.pre_flags)
? `${construction.time} m`
: (construction.time ?? "0 m")}
</dd>
{#if construction.pre_terrain}
{#if prerequisites.length}
<dt>{t("Requires", { _context })}</dt>
<dd>
<ItemLink
type={construction.pre_terrain.startsWith("f_")
? "furniture"
: "terrain"}
id={construction.pre_terrain} />
<ul class="comma-separated">
{#each prerequisites as prerequisite}
<li>
<ItemLink type={prerequisite.type} id={prerequisite.id} />
</li>
{/each}
</ul>
</dd>
{/if}
{#if preFlags.length}
Expand Down Expand Up @@ -118,23 +127,25 @@ if (construction.pre_flags)
</ul>
</dd>
{/if}
{#if !includeTitle && construction.post_terrain}
{#if !includeTitle && results.length}
<dt>{t("Creates", { _context })}</dt>
<dd>
{#if construction.post_terrain === "f_null"}
<em
>{t("nothing", {
_context,
_comment:
'The furniture/terrain "created" by a deconstruction is...',
})}</em>
{:else}
<ItemLink
type={construction.post_terrain.startsWith("f_")
? "furniture"
: "terrain"}
id={construction.post_terrain} />
{/if}
<ul class="comma-separated">
{#each results as result}
<li>
{#if isNullConstructionResult(result)}
<em
>{t("nothing", {
_context,
_comment:
'The furniture/terrain "created" by a deconstruction is...',
})}</em>
{:else}
<ItemLink type={result.type} id={result.id} />
{/if}
</li>
{/each}
</ul>
</dd>
{/if}
</dl>
Expand Down
92 changes: 92 additions & 0 deletions src/types/Construction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* @vitest-environment happy-dom
*/
import { render } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
import WithData from "../WithData.svelte";
import { CBNData } from "../data";
import Construction from "./Construction.svelte";

describe("Construction", () => {
it("renders furniture prerequisites and null furniture results", () => {
const data = new CBNData([
{
type: "construction_group",
id: "advanced_object_deconstruction",
name: "Advanced Object Deconstruction",
},
{
type: "construction",
id: "constr_remove_object_fireplace",
group: "advanced_object_deconstruction",
category: "OTHER",
time: "90 m",
required_skills: [["fabrication", 2]],
pre_furniture: "f_fireplace",
post_furniture: "f_null",
},
{
type: "furniture",
id: "f_fireplace",
name: "fireplace",
description: "A warm test fixture.",
move_cost_mod: 0,
required_str: 0,
},
]);

const { getByText } = render(WithData, {
Component: Construction,
data,
construction: data.byId("construction", "constr_remove_object_fireplace"),
});

expect(getByText("Requires")).toBeTruthy();
expect(getByText("fireplace")).toBeTruthy();
expect(getByText("Creates")).toBeTruthy();
expect(getByText("nothing")).toBeTruthy();
});

it("renders terrain prerequisites and furniture results without prefix inference", () => {
const data = new CBNData([
{
type: "construction_group",
id: "build_beaded_door",
name: "Build Beaded Door",
},
{
type: "construction",
id: "constr_beaded_door",
group: "build_beaded_door",
category: "OTHER",
time: "30 m",
required_skills: [["fabrication", 1]],
pre_terrain: "t_door_frame",
post_furniture: "f_beaded_door",
},
{
type: "terrain",
id: "t_door_frame",
name: "door frame",
description: "A test door frame.",
},
{
type: "furniture",
id: "f_beaded_door",
name: "beaded door",
description: "A test beaded door.",
move_cost_mod: 0,
required_str: 0,
},
]);

const { getByText } = render(WithData, {
Component: Construction,
data,
construction: data.byId("construction", "constr_beaded_door"),
});

expect(getByText("door frame")).toBeTruthy();
expect(getByText("beaded door")).toBeTruthy();
});
});
2 changes: 1 addition & 1 deletion src/types/Furniture.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const bash = item.bash?.items

const constructions = data
.byType("construction")
.filter((c) => c.post_terrain === item.id);
.filter((c) => c.post_furniture === item.id);

const bashedFrom = data
.byType("furniture")
Expand Down
67 changes: 67 additions & 0 deletions src/types/Furniture.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @vitest-environment happy-dom
*/
import { render } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
import WithData from "../WithData.svelte";
import { CBNData } from "../data";
import Furniture from "./Furniture.svelte";

describe("Furniture", () => {
it("shows furniture constructions from post_furniture", () => {
const data = new CBNData([
{
type: "construction_group",
id: "build_sign",
name: "Build Sign",
},
{
type: "construction_group",
id: "dig_pit",
name: "Dig Pit",
},
{
type: "construction",
id: "constr_sign",
group: "build_sign",
category: "OTHER",
time: "20 m",
required_skills: [["fabrication", 0]],
post_furniture: "f_sign",
},
{
type: "construction",
id: "constr_pit",
group: "dig_pit",
category: "OTHER",
time: "30 m",
required_skills: [["fabrication", 0]],
post_terrain: "t_pit",
},
{
type: "furniture",
id: "f_sign",
name: "sign",
description: "Read it. Warnings ahead.",
move_cost_mod: 0,
required_str: 0,
},
{
type: "terrain",
id: "t_pit",
name: "pit",
description: "A test pit.",
},
]);
Comment on lines +49 to +55
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Test fixture incomplete but non-critical.

Terrain record at Lines 50-54 lacks required schema fields (symbol, color, move_cost). CBNData constructor may tolerate partial records for test purposes. If strict validation is later enforced, this fixture will require augmentation.

🔧 Optional: Complete terrain fixture
       {
         type: "terrain",
         id: "t_pit",
         name: "pit",
         description: "A test pit.",
+        symbol: "0",
+        color: "brown",
+        move_cost: 10,
       },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
type: "terrain",
id: "t_pit",
name: "pit",
description: "A test pit.",
},
]);
{
type: "terrain",
id: "t_pit",
name: "pit",
description: "A test pit.",
symbol: "0",
color: "brown",
move_cost: 10,
},
]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/Furniture.test.ts` around lines 49 - 55, The terrain test fixture
object in Furniture.test.ts (the terrain entry in the test data array) is
missing required schema fields `symbol`, `color`, and `move_cost`, which may
break future strict validation; update the terrain object in the test fixture to
include these fields (e.g., add `symbol`, `color`, and `move_cost`) so it
conforms to the schema used by the CBNData constructor/validation logic (refer
to the terrain object in the test data array and CBNData usage to locate where
to update).


const { getByText, queryByText } = render(WithData, {
Component: Furniture,
data,
item: data.byId("furniture", "f_sign"),
});

expect(getByText("Construction")).toBeTruthy();
expect(getByText("Build Sign")).toBeTruthy();
expect(queryByText("Dig Pit")).toBeNull();
});
});
17 changes: 9 additions & 8 deletions src/types/ToolQuality.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { getContext, untrack } from "svelte";
import { CBNData } from "../data";
import LimitedList from "../LimitedList.svelte";
import type { Construction, Item, ToolQuality, VehiclePart } from "../types";
import { getConstructionPrerequisites } from "./construction";
import ItemLink from "./ItemLink.svelte";
import { byName, i18n, gameSingularName } from "../utils/i18n";
import { byName, gameSingularName } from "../utils/i18n";

interface Props {
item: ToolQuality;
Expand Down Expand Up @@ -206,13 +207,13 @@ constructionsUsingQualityByLevelList.forEach(([, constructions]) => {
id={f.group}
type="construction_group"
showIcon={false} />
{#if f.pre_terrain}
on {#each [f.pre_terrain].flat() as preTerrain, i}
{@const itemType = preTerrain.startsWith("f_")
? "furniture"
: "terrain"}
{#if i !== 0}{i18n.__(" OR ")}{/if}
<ItemLink type={itemType} id={preTerrain} />
{@const prerequisites = getConstructionPrerequisites(f)}
{#if prerequisites.length}
{t("on")}
{#each prerequisites as prerequisite, i}
{#if i !== 0},
{/if}
<ItemLink type={prerequisite.type} id={prerequisite.id} />
Comment on lines +210 to +216
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Localize the new prerequisite separator.

The raw on literal is now shipped in UI output, so translated builds will leak English here. The same fragment was copied into src/types/item/ConstructionByproduct.svelte (Lines 36-41) and src/types/item/ComponentOf.svelte (Lines 132-137 and 155-160); route it through t(...) or extract a shared renderer before this drifts further.

As per coding guidelines, "Use t from @transifex/native for all user-facing text".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/ToolQuality.svelte` around lines 210 - 215, The UI currently
outputs a raw "on" literal in the prerequisites list (in the block that calls
getConstructionPrerequisites and renders ItemLink), which must be localized;
replace the hardcoded "on" with a localized string using t from
`@transifex/native` (e.g., t('on')) or extract a small shared renderer/helper to
render the separator so the same localized string is reused in
ConstructionByproduct.svelte and ComponentOf.svelte; update the occurrences
around getConstructionPrerequisites/ItemLink and the matching fragments in
ConstructionByproduct and ComponentOf to use the t(...) call or the shared
component instead of the raw literal so all user-facing text is localized.

{/each}
{/if}
{/snippet}
Expand Down
53 changes: 53 additions & 0 deletions src/types/construction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { Construction } from "../types";

export type ConstructionSurfaceTarget = {
id: string;
type: "terrain" | "furniture";
};

/**
* Collect prerequisite terrain/furniture targets using explicit fields.
*/
export function getConstructionPrerequisites(
construction: Pick<Construction, "pre_terrain" | "pre_furniture">,
): ConstructionSurfaceTarget[] {
const prerequisites: ConstructionSurfaceTarget[] = [];

if (construction.pre_terrain) {
prerequisites.push({ id: construction.pre_terrain, type: "terrain" });
}

if (construction.pre_furniture) {
prerequisites.push({ id: construction.pre_furniture, type: "furniture" });
}

return prerequisites;
}

/**
* Collect output terrain/furniture targets using explicit fields.
*/
export function getConstructionResults(
construction: Pick<Construction, "post_terrain" | "post_furniture">,
): ConstructionSurfaceTarget[] {
const results: ConstructionSurfaceTarget[] = [];

if (construction.post_terrain) {
results.push({ id: construction.post_terrain, type: "terrain" });
}

if (construction.post_furniture) {
results.push({ id: construction.post_furniture, type: "furniture" });
}

return results;
}

export function isNullConstructionResult(
result: ConstructionSurfaceTarget,
): boolean {
return (
(result.type === "terrain" && result.id === "t_null") ||
(result.type === "furniture" && result.id === "f_null")
);
}
Comment on lines +11 to +53
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Advisory: Consider JSDoc for public API surface.

Per coding guidelines, public functions warrant JSDoc documentation. Current signatures are self-documenting, but brief descriptions would enhance IDE telemetry and onboarding velocity for downstream consumers.

📝 Optional: Add JSDoc headers
+/**
+ * Extracts prerequisite surfaces (terrain/furniture) from a construction definition.
+ */
 export function getConstructionPrerequisites(
   construction: Pick<Construction, "pre_terrain" | "pre_furniture">,
 ): ConstructionSurfaceTarget[] {
+/**
+ * Extracts result surfaces (terrain/furniture) from a construction definition.
+ */
 export function getConstructionResults(
   construction: Pick<Construction, "post_terrain" | "post_furniture">,
 ): ConstructionSurfaceTarget[] {
+/**
+ * Returns true if the target represents a null/removal result (t_null or f_null).
+ */
 export function isNullConstructionResult(
   result: ConstructionSurfaceTarget,
 ): boolean {

As per coding guidelines: "Use JSDoc comments for public functions and complex logic."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function getConstructionPrerequisites(
construction: Pick<Construction, "pre_terrain" | "pre_furniture">,
): ConstructionSurfaceTarget[] {
const prerequisites: ConstructionSurfaceTarget[] = [];
if (construction.pre_terrain) {
prerequisites.push({ id: construction.pre_terrain, type: "terrain" });
}
if (construction.pre_furniture) {
prerequisites.push({ id: construction.pre_furniture, type: "furniture" });
}
return prerequisites;
}
export function getConstructionResults(
construction: Pick<Construction, "post_terrain" | "post_furniture">,
): ConstructionSurfaceTarget[] {
const results: ConstructionSurfaceTarget[] = [];
if (construction.post_terrain) {
results.push({ id: construction.post_terrain, type: "terrain" });
}
if (construction.post_furniture) {
results.push({ id: construction.post_furniture, type: "furniture" });
}
return results;
}
export function isNullConstructionResult(
result: ConstructionSurfaceTarget,
): boolean {
return (
(result.type === "terrain" && result.id === "t_null") ||
(result.type === "furniture" && result.id === "f_null")
);
}
/**
* Extracts prerequisite surfaces (terrain/furniture) from a construction definition.
*/
export function getConstructionPrerequisites(
construction: Pick<Construction, "pre_terrain" | "pre_furniture">,
): ConstructionSurfaceTarget[] {
const prerequisites: ConstructionSurfaceTarget[] = [];
if (construction.pre_terrain) {
prerequisites.push({ id: construction.pre_terrain, type: "terrain" });
}
if (construction.pre_furniture) {
prerequisites.push({ id: construction.pre_furniture, type: "furniture" });
}
return prerequisites;
}
/**
* Extracts result surfaces (terrain/furniture) from a construction definition.
*/
export function getConstructionResults(
construction: Pick<Construction, "post_terrain" | "post_furniture">,
): ConstructionSurfaceTarget[] {
const results: ConstructionSurfaceTarget[] = [];
if (construction.post_terrain) {
results.push({ id: construction.post_terrain, type: "terrain" });
}
if (construction.post_furniture) {
results.push({ id: construction.post_furniture, type: "furniture" });
}
return results;
}
/**
* Returns true if the target represents a null/removal result (t_null or f_null).
*/
export function isNullConstructionResult(
result: ConstructionSurfaceTarget,
): boolean {
return (
(result.type === "terrain" && result.id === "t_null") ||
(result.type === "furniture" && result.id === "f_null")
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/construction.ts` around lines 8 - 47, Add JSDoc comments for the
public functions getConstructionPrerequisites, getConstructionResults, and
isNullConstructionResult: for each function include a one-line description, a
`@param` describing the construction parameter shape (e.g., Pick<Construction,
"..."> and meaning of fields like pre_terrain/pre_furniture or
post_terrain/post_furniture), and a `@returns` describing the
ConstructionSurfaceTarget[] (or boolean for isNullConstructionResult) and what
the values represent (terrain vs furniture and null ids t_null/f_null). Keep the
comments brief and place them immediately above each function so IDEs and docs
can pick them up.

Loading
Loading