Skip to content

Commit daecbdc

Browse files
authored
Merge pull request #226 from VolvoxCommunity/bill/add-volvoxbot-to-products
feat(products): add Volvox.Bot to product listings
2 parents 077b9e9 + a2590a8 commit daecbdc

17 files changed

Lines changed: 503 additions & 82 deletions

content/products.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
11
[
2+
{
3+
"id": "c9c6e9f4-0f69-4cce-bc38-5f5f42ac8b3d",
4+
"name": "Volvox.Bot",
5+
"description": "An AI-powered Discord bot that brings chat, moderation, summaries, memory, XP, and community tooling into one command center.",
6+
"longDescription": "Volvox.Bot brings AI-native community tooling to Discord servers. It supports context-aware AI chat, model-selectable auto-moderation with configurable actions, reputation and XP tracking, long-term user memory, and TL;DR summaries for catching up on busy conversations. The hosted dashboard gives community operators a single place to configure and monitor the bot.",
7+
"features": [
8+
"AI chat for context-aware Discord replies",
9+
"AI auto-moderation with configurable thresholds and actions",
10+
"Reputation and XP tracking with role rewards",
11+
"Long-term user memory for personalized interactions",
12+
"TL;DR summaries for busy Discord conversations",
13+
"Hosted dashboard for community configuration"
14+
],
15+
"demoUrl": "https://volvox.bot",
16+
"image": "/logo.png"
17+
},
218
{
319
"id": "ee7a459b-9319-4568-8c70-a9842e3c3558",
420
"name": "Sobers",
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"id": "c9c6e9f4-0f69-4cce-bc38-5f5f42ac8b3d",
3+
"name": "Volvox.Bot",
4+
"type": "Discord Bot",
5+
"slug": "volvox-bot",
6+
"tagline": "AI for Discord communities.",
7+
"description": "An AI-powered Discord bot that brings chat, auto-moderation, XP, community tooling, and more into one command center.",
8+
"longDescription": "Volvox.Bot brings AI-native community tooling to Discord servers. It supports context-aware AI chat, model-selectable auto-moderation with configurable actions, reputation and XP tracking, long-term user memory, and TL;DR summaries for catching up on busy conversations. The hosted dashboard gives community operators a single place to configure and monitor the bot.",
9+
"features": [
10+
"AI chat for context-aware Discord replies",
11+
"AI auto-moderation with configurable thresholds and actions",
12+
"Reputation and XP tracking with role rewards",
13+
"Long-term user memory for personalized interactions",
14+
"TL;DR summaries for busy Discord conversations",
15+
"Hosted dashboard for community configuration"
16+
],
17+
"techStack": [
18+
"Discord",
19+
"Bot",
20+
"AI",
21+
"Moderation",
22+
"Analytics"
23+
],
24+
"updatedAt": "2026-06-07T00:00:00Z",
25+
"links": {
26+
"demo": "https://volvox.bot"
27+
},
28+
"screenshots": [
29+
"/images/product/volvox-bot/icon.png",
30+
"/images/product/volvox-bot/analytics.png"
31+
],
32+
"faq": [
33+
{
34+
"question": "What does Volvox.Bot do?",
35+
"answer": "Volvox.Bot brings AI chat, AI-powered auto-moderation, TL;DR summaries, user memory, reputation (XP), community analytics, and much more to your Discord server."
36+
},
37+
{
38+
"question": "Where can I try Volvox.Bot?",
39+
"answer": "Visit https://volvox.bot to open the dashboard or add the bot to a Discord server."
40+
},
41+
{
42+
"question": "How much does Volvox.Bot cost?",
43+
"answer": "Volvox.Bot is completely free to use and has no hidden fees or ads."
44+
},
45+
{
46+
"question": "What is the hosted dashboard?",
47+
"answer": "The hosted dashboard is a web interface that allows you to configure and monitor the bot. It is hosted on our own servers and is completely free to use."
48+
}
49+
],
50+
"testimonials": []
51+
}

e2e/product-default-sort.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
import { getAllExtendedProducts } from "../src/lib/content";
4+
import { sortProductsByNewest } from "../src/lib/product-sorting";
5+
6+
function getExpectedNewestProductNames(): string[] {
7+
return getAllExtendedProducts()
8+
.sort(sortProductsByNewest)
9+
.map((product) => product.name);
10+
}
11+
12+
test("products page defaults to newest products first", async ({ page }) => {
13+
await page.goto("/products");
14+
15+
await expect(page).not.toHaveURL(/sort=/);
16+
await expect(
17+
page.locator('section[aria-label="Products list"] h3'),
18+
).toHaveText(getExpectedNewestProductNames());
19+
});
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
const DECISION_JAR_FEATURES = [
4+
"Shake-to-decide or tap for random selection from custom jars",
5+
"AI-powered suggestions to expand your options (up to 50/day on Premium)",
6+
"Decision history tracking to identify choice patterns",
7+
"QR code sharing for instant jar importing with friends",
8+
];
9+
10+
const SOBERS_FEATURES = [
11+
"Secure sponsor-sponsee pairing with invite code system",
12+
"Sobriety timeline with transparent relapse tracking",
13+
"Task management with sponsor assignment and sponsee completion notes",
14+
"Visual journey timeline displaying milestones, tasks, and step progress",
15+
];
16+
17+
const VOLVOX_BOT_FEATURES = [
18+
"AI chat for context-aware Discord replies",
19+
"AI auto-moderation with configurable thresholds and actions",
20+
"Reputation and XP tracking with role rewards",
21+
"Long-term user memory for personalized interactions",
22+
];
23+
24+
const HOMEPAGE_FEATURES = [
25+
...DECISION_JAR_FEATURES,
26+
...SOBERS_FEATURES,
27+
...VOLVOX_BOT_FEATURES,
28+
];
29+
30+
type FeatureRenderState = {
31+
clientWidth: number;
32+
isHorizontallyClipped: boolean;
33+
isMissing: boolean;
34+
overflow: string;
35+
scrollWidth: number;
36+
text: string;
37+
textOverflow: string;
38+
whiteSpace: string;
39+
};
40+
41+
test("homepage product feature previews show their full text", async ({
42+
page,
43+
}) => {
44+
await page.setViewportSize({ width: 680, height: 743 });
45+
await page.goto("/");
46+
47+
const renderStates = await page.evaluate((featureTexts) => {
48+
const productSection = document.querySelector("#products");
49+
if (!productSection) {
50+
throw new Error("Products section was not rendered");
51+
}
52+
53+
return featureTexts.map((text): FeatureRenderState => {
54+
const featureElement = Array.from(
55+
productSection.querySelectorAll("span"),
56+
).find((element) => element.textContent?.trim() === text);
57+
58+
if (!featureElement) {
59+
return {
60+
clientWidth: 0,
61+
isHorizontallyClipped: false,
62+
isMissing: true,
63+
overflow: "",
64+
scrollWidth: 0,
65+
text,
66+
textOverflow: "",
67+
whiteSpace: "",
68+
};
69+
}
70+
71+
const computedStyle = window.getComputedStyle(featureElement);
72+
73+
return {
74+
clientWidth: featureElement.clientWidth,
75+
isHorizontallyClipped:
76+
featureElement.scrollWidth > featureElement.clientWidth,
77+
isMissing: false,
78+
overflow: computedStyle.overflow,
79+
scrollWidth: featureElement.scrollWidth,
80+
text,
81+
textOverflow: computedStyle.textOverflow,
82+
whiteSpace: computedStyle.whiteSpace,
83+
};
84+
});
85+
}, HOMEPAGE_FEATURES);
86+
87+
expect(renderStates).toEqual(
88+
expect.arrayContaining(
89+
HOMEPAGE_FEATURES.map((text) =>
90+
expect.objectContaining({
91+
isHorizontallyClipped: false,
92+
isMissing: false,
93+
overflow: "visible",
94+
text,
95+
textOverflow: "clip",
96+
whiteSpace: "normal",
97+
}),
98+
),
99+
),
100+
);
101+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
const PRODUCT_OVERVIEW_LINKS = [
4+
{ name: "Volvox.Bot", path: "/products/volvox-bot" },
5+
{ name: "Decision Jar", path: "/products/decision-jar" },
6+
{ name: "Sobers", path: "/products/sobers" },
7+
];
8+
9+
test("homepage product overview controls link to product detail pages", async ({
10+
page,
11+
}) => {
12+
await page.setViewportSize({ width: 680, height: 743 });
13+
await page.goto("/");
14+
15+
for (const product of PRODUCT_OVERVIEW_LINKS) {
16+
const productCard = page
17+
.locator("#products .group.relative")
18+
.filter({ hasText: product.name });
19+
const overviewLink = productCard.getByRole("link", {
20+
exact: true,
21+
name: "Overview",
22+
});
23+
24+
await expect(overviewLink).toHaveAttribute("href", product.path);
25+
}
26+
});

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"lint": "biome check",
1111
"lint:fix": "biome check --fix",
1212
"typecheck": "tsc --noEmit",
13-
"test": "tsx --test tests/**/*.test.ts",
13+
"test": "tsx --test --import ./tests/setup-globals.ts tests/**/*.test.ts",
1414
"format": "biome format --write .",
1515
"format:check": "biome format .",
1616
"prepare": "husky"
@@ -75,6 +75,7 @@
7575
"@types/react": "^19.2.14",
7676
"@types/react-dom": "^19.2.3",
7777
"dotenv": "^17.4.2",
78+
"expect": "30.4.1",
7879
"husky": "^9.1.7",
7980
"lint-staged": "^17.0.5",
8081
"playwright": "^1.60.0",

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
573 KB
Loading
4.09 KB
Loading

0 commit comments

Comments
 (0)