Skip to content
Open
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
82 changes: 0 additions & 82 deletions STYLING.md

This file was deleted.

2 changes: 1 addition & 1 deletion api/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default async function handler(_request: Request) {
}));

// Combine both lists
const allApps = [...dbApps, ...submissionsAsApps];
const allApps = [...officialApps, ...submissionsAsApps];

// Get today's featured app (if any)
const today = new Date().toISOString().split('T')[0];
Expand Down
4 changes: 2 additions & 2 deletions claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -981,9 +981,9 @@ mkdir extension
3. User types app name → autocomplete suggests existing apps to prevent duplicates
4. If selecting existing app → yellow warning appears
5. Form validates and submits with `status: 'pending'`
6. **Submitter sees their app immediately in dashboard**
6. **App shows immediately on user's personal profile (`/[slug]`)**
7. Admin reviews via Drizzle Studio
8. Approved → visible globally in app directory
8. **Approved** → visible globally on main `string.sg` homepage and searchable in app directory
Comment on lines +984 to +986
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

The PR removes STYLING.md, but claude.md still lists STYLING.md in the repository tree earlier in the document. Update that section to avoid stale documentation references (or adjust the PR scope if the file is meant to remain).

Copilot uses AI. Check for mistakes.

### From Profile Page (NEW)
1. User visits their own profile → Sees "+ Add App" button (if no apps yet)
Expand Down
38 changes: 36 additions & 2 deletions data/apps-seed.json
Original file line number Diff line number Diff line change
Expand Up @@ -637,8 +637,7 @@
"transcription"
],
"is_official": true,
"frequency": 0,
"featured": true
"frequency": 0
},
{
"name": "SmartCompose",
Expand Down Expand Up @@ -706,6 +705,41 @@
"is_official": true,
"frequency": 0,
"logo_url": "/src/app-icons/langbuddy.png"
},
{
"name": "Write Formula Game",
"slug": "write-formula-game",
"url": "https://writeformulagame.com/",
"description": "Interactive game for practising chemistry formula writing",
"tagline": "Make formula writing fun",
"category": "Teaching",
"tags": ["science"],
"is_official": false,
"frequency": 0
},
{
"name": "String Buy",
"slug": "buy",
"url": "https://buy.string.sg/",
"description": "Simulate a pit market and visualise demand-supply equilibrium with live price discovery",
"tagline": "Pit market demand-supply simulator",
"category": "Teaching",
"tags": ["economics", "simulation", "games", "classroom"],
"is_official": false,
"frequency": 0,
"featured": true
},
{
"name": "String Diagrams",
"slug": "diagrams",
"url": "https://diagrams.string.sg/",
"description": "Generate circuit diagrams and build isometric cube illustrations for lessons",
"tagline": "Circuit diagrams & isometric cubes",
"category": "Teaching",
"tags": ["science", "physics", "diagrams", "drawing"],
"is_official": false,
"frequency": 0,
"featured": true
}
],
"categories": [
Expand Down
48 changes: 35 additions & 13 deletions scripts/seed-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dotenv/config';
import { neon } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-http';
import { apps, bumpRules, categories } from '../src/db/schema';
import { eq } from 'drizzle-orm';
import seedData from '../data/apps-seed.json';

async function main() {
Expand All @@ -31,12 +32,12 @@ async function main() {
}
console.log(` ✓ ${seedData.categories.length} categories\n`);

// Seed apps
// Seed apps (upsert — re-running always reflects the JSON)
console.log('Seeding apps...');
const appIdMap: Record<string, string> = {};

for (const app of seedData.apps) {
const [inserted] = await db.insert(apps).values({
const values = {
name: app.name,
slug: app.slug,
url: app.url,
Expand All @@ -47,26 +48,47 @@ async function main() {
isOfficial: app.is_official,
frequency: app.frequency,
featured: app.featured || false,
}).onConflictDoNothing().returning({ id: apps.id });
};

if (inserted) {
appIdMap[app.slug] = inserted.id;
console.log(` ✓ ${app.name}`);
} else {
console.log(` - ${app.name} (already exists)`);
}
const [upserted] = await db.insert(apps)
.values(values)
.onConflictDoUpdate({
target: apps.slug,
set: {
name: values.name,
url: values.url,
description: values.description,
tagline: values.tagline,
category: values.category,
tags: values.tags,
isOfficial: values.isOfficial,
frequency: values.frequency,
featured: values.featured,
updatedAt: new Date(),
},
})
.returning({ id: apps.id });

appIdMap[app.slug] = upserted.id;
console.log(` ✓ ${app.name}`);
}
console.log(`\n Total: ${Object.keys(appIdMap).length} apps seeded\n`);
console.log(`\n Total: ${Object.keys(appIdMap).length} apps upserted\n`);

// Seed bump rules for apps that have them
// Seed bump rules (delete + reinsert per app so JSON stays authoritative)
console.log('Seeding bump rules...');
let ruleCount = 0;

for (const app of seedData.apps) {
if (app.bump_rules && appIdMap[app.slug]) {
if (!appIdMap[app.slug]) continue;
const appId = appIdMap[app.slug];

// Clear existing rules for this app before reinserting
await db.delete(bumpRules).where(eq(bumpRules.appId, appId));

if (app.bump_rules) {
Comment on lines 81 to +88
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

This seeder now unconditionally deletes all bump rules for every app (even when app.bump_rules is missing), which can wipe manually-managed rules and makes db:seed destructive. Consider only deleting/replacing rules when bump rules are present in the JSON (or add an explicit --force/confirmation flag) to avoid accidental data loss.

Copilot uses AI. Check for mistakes.
for (const rule of app.bump_rules) {
await db.insert(bumpRules).values({
appId: appIdMap[app.slug],
appId,
ruleType: rule.type,
startTime: rule.start || null,
endTime: rule.end || null,
Expand Down