+ How a non-consensual deepfake spreads through a simulated school year
+ group, and what an education program changes. Illustrative, not
+ validated.
+
- You’ve successfully created a project with
- Vite +
- Vue 3.
-
-
-
-
-
diff --git a/web/src/components/TheWelcome.vue b/web/src/components/TheWelcome.vue
deleted file mode 100644
index 8b731d9..0000000
--- a/web/src/components/TheWelcome.vue
+++ /dev/null
@@ -1,95 +0,0 @@
-
-
-
-
-
-
-
- Documentation
-
- Vue’s
- official documentation
- provides you with all information you need to get started.
-
-
-
-
-
-
- Tooling
-
- This project is served and bundled with
- Vite. The
- recommended IDE setup is
- VSCode
- +
- Vue - Official. If you need to test your components and web pages, check out
- Vitest
- and
- Cypress
- /
- Playwright.
-
-
-
- More instructions are available in
- README.md.
-
-
-
-
-
-
- Ecosystem
-
- Get official tools and libraries for your project:
- Pinia,
- Vue Router,
- Vue Test Utils, and
- Vue Dev Tools. If
- you need more resources, we suggest paying
- Awesome Vue
- a visit.
-
-
-
-
-
-
- Community
-
- Got stuck? Ask your question on
- Vue Land
- (our official Discord server), or
- StackOverflow. You should also follow the official
- @vuejs.org
- Bluesky account or the
- @vuejs
- X account for latest news in the Vue world.
-
-
-
-
-
-
- Support Vue
-
- As an independent project, Vue relies on community backing for its sustainability. You can help
- us by
- becoming a sponsor.
-
-
diff --git a/web/src/components/WelcomeItem.vue b/web/src/components/WelcomeItem.vue
deleted file mode 100644
index 6d7086a..0000000
--- a/web/src/components/WelcomeItem.vue
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/web/src/components/__tests__/ComparisonTable.spec.ts b/web/src/components/__tests__/ComparisonTable.spec.ts
new file mode 100644
index 0000000..810c3b4
--- /dev/null
+++ b/web/src/components/__tests__/ComparisonTable.spec.ts
@@ -0,0 +1,68 @@
+import { describe, it, expect } from 'vitest'
+import { mount } from '@vue/test-utils'
+import ComparisonTable from '../ComparisonTable.vue'
+import type { ComparisonResponse } from '@/types/api'
+
+// A small hand-made comparison; the real numbers come from the API and are
+// pinned by the Go tests. Here we only care that the table renders them.
+const comparison: ComparisonResponse = {
+ config: {
+ numStudents: 10,
+ edgesPerNode: 2,
+ triangleProb: 0.4,
+ forwardProb: 0.5,
+ numEducated: 3,
+ origin: 0,
+ graphSeed: 1,
+ thresholdSeed: 2,
+ educationSeed: 3,
+ },
+ results: [
+ {
+ strategy: 'none',
+ educated: [],
+ reachedAtRound: [0, 1, 1, 2, 2, 2, 3, -1, -1, -1],
+ numReached: 7,
+ numRounds: 4,
+ reachedPct: 70,
+ },
+ {
+ strategy: 'random',
+ educated: [2, 5, 8],
+ reachedAtRound: [0, 1, 1, 2, -1, -1, -1, -1, -1, -1],
+ numReached: 4,
+ numRounds: 3,
+ reachedPct: 40,
+ },
+ {
+ strategy: 'most-connected',
+ educated: [1, 2, 3],
+ reachedAtRound: [0, 1, -1, -1, -1, -1, -1, -1, -1, -1],
+ numReached: 2,
+ numRounds: 2,
+ reachedPct: 20,
+ },
+ ],
+}
+
+describe('ComparisonTable', () => {
+ it('renders one row per strategy with its reach', () => {
+ const wrapper = mount(ComparisonTable, { props: { comparison } })
+
+ const rows = wrapper.findAll('tbody tr')
+ expect(rows).toHaveLength(3)
+ expect(rows[0]!.text()).toContain('No program')
+ expect(rows[0]!.text()).toContain('7 / 10')
+ expect(rows[2]!.text()).toContain('Educate the most connected')
+ expect(rows[2]!.text()).toContain('20%')
+ })
+
+ it('falls back to the raw strategy name for unknown strategies', () => {
+ const unknown: ComparisonResponse = {
+ config: comparison.config,
+ results: [{ ...comparison.results[0]!, strategy: 'telepathy' }],
+ }
+ const wrapper = mount(ComparisonTable, { props: { comparison: unknown } })
+ expect(wrapper.find('tbody th').text()).toBe('telepathy')
+ })
+})
diff --git a/web/src/components/__tests__/HelloWorld.spec.ts b/web/src/components/__tests__/HelloWorld.spec.ts
deleted file mode 100644
index 2533202..0000000
--- a/web/src/components/__tests__/HelloWorld.spec.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { describe, it, expect } from 'vitest'
-
-import { mount } from '@vue/test-utils'
-import HelloWorld from '../HelloWorld.vue'
-
-describe('HelloWorld', () => {
- it('renders properly', () => {
- const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
- expect(wrapper.text()).toContain('Hello Vitest')
- })
-})
diff --git a/web/src/components/icons/IconCommunity.vue b/web/src/components/icons/IconCommunity.vue
deleted file mode 100644
index 2dc8b05..0000000
--- a/web/src/components/icons/IconCommunity.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/web/src/components/icons/IconDocumentation.vue b/web/src/components/icons/IconDocumentation.vue
deleted file mode 100644
index 6d4791c..0000000
--- a/web/src/components/icons/IconDocumentation.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/web/src/components/icons/IconEcosystem.vue b/web/src/components/icons/IconEcosystem.vue
deleted file mode 100644
index c3a4f07..0000000
--- a/web/src/components/icons/IconEcosystem.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/web/src/components/icons/IconSupport.vue b/web/src/components/icons/IconSupport.vue
deleted file mode 100644
index 7452834..0000000
--- a/web/src/components/icons/IconSupport.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/web/src/components/icons/IconTooling.vue b/web/src/components/icons/IconTooling.vue
deleted file mode 100644
index 660598d..0000000
--- a/web/src/components/icons/IconTooling.vue
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts
new file mode 100644
index 0000000..7dd3210
--- /dev/null
+++ b/web/src/lib/api.ts
@@ -0,0 +1,27 @@
+import type { Config } from '@/types/engine'
+import type { ComparisonResponse } from '@/types/api'
+
+// Thin typed wrappers around the JSON API. The types come from
+// src/types/, which is generated from the Go structs (single source of
+// truth); nothing here redefines a shape.
+
+async function requestJSON(input: string, init?: RequestInit): Promise {
+ const response = await fetch(input, init)
+ if (!response.ok) {
+ const body = await response.text()
+ throw new Error(`${response.status} ${response.statusText}: ${body}`)
+ }
+ return response.json() as Promise
+}
+
+export function fetchDefaultConfig(): Promise {
+ return requestJSON('/api/config/default')
+}
+
+export function runComparison(config: Config): Promise {
+ return requestJSON('/api/comparison', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(config),
+ })
+}
diff --git a/web/vite.config.ts b/web/vite.config.ts
index 4217010..4939dcf 100644
--- a/web/vite.config.ts
+++ b/web/vite.config.ts
@@ -15,4 +15,11 @@ export default defineConfig({
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
+ server: {
+ // The Go server owns /api; run it next to `npm run dev` with:
+ // go run ./cmd/spreadlab
+ proxy: {
+ '/api': 'http://localhost:8080',
+ },
+ },
})