@wenathlan/cobaia 0.0.0-stage → 2.0.158

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/App.tsx +52 -0
  2. package/Dockerfile +48 -0
  3. package/_headers +4 -0
  4. package/_redirects +1 -0
  5. package/adsconfig.ts +59 -0
  6. package/biome.jsonc +134 -0
  7. package/bun.lock +2451 -0
  8. package/capacitor.config.ts +51 -0
  9. package/checkoutlinks.ts +80 -0
  10. package/cli.ts +114 -0
  11. package/cobaia.csproj +22 -0
  12. package/cobaia.gemspec +14 -0
  13. package/cobaia.java +16 -0
  14. package/content.ts +425 -0
  15. package/db.ts +219 -0
  16. package/devthink.toml +5 -0
  17. package/docs/features.md +58 -0
  18. package/docs/monetization.md +61 -0
  19. package/familyurl.ts +22 -0
  20. package/favicon.svg +1 -0
  21. package/icon.svg +1 -0
  22. package/index.html +26 -0
  23. package/labels.ts +291 -0
  24. package/manifest.webmanifest +56 -0
  25. package/mime.types +12 -0
  26. package/netlify.toml +2 -0
  27. package/package.json +207 -4
  28. package/pnpm-workspace.yaml +101 -0
  29. package/pnpmboot.package-lock.json +157 -0
  30. package/pnpmboot.package.json +6 -0
  31. package/pom.xml +29 -0
  32. package/posts.ts +54 -0
  33. package/robots.txt +90 -0
  34. package/roster.ts +29 -0
  35. package/sandboxmodality.ts +138 -0
  36. package/schema.prisma +31 -0
  37. package/sitemap.xml +42 -0
  38. package/sol/Sol.tsx +91 -0
  39. package/sol/apps/apps.tsx +66 -0
  40. package/sol/blog/blog.tsx +86 -0
  41. package/sol/home/home.tsx +140 -0
  42. package/sol/intro/intro.tsx +187 -0
  43. package/sol/legal/legal.tsx +76 -0
  44. package/sol/notfound/notfound.tsx +32 -0
  45. package/sol/pricing/pricing.tsx +98 -0
  46. package/sol/shell/Shell.tsx +161 -0
  47. package/sol/sol.css +1951 -0
  48. package/sol/support/support.tsx +102 -0
  49. package/sol/wingets/IntroFooter.tsx +69 -0
  50. package/sol/wingets/IntroPlans.tsx +93 -0
  51. package/sol/wingets/IntroSplash.tsx +105 -0
  52. package/sol/wingets/ShellAdsslot.tsx +47 -0
  53. package/sol/wingets/ShellBrand.tsx +86 -0
  54. package/sol/wingets/index.ts +18 -0
  55. package/tests/adsconfig.test.ts +51 -0
  56. package/tests/checkoutlinks.test.ts +73 -0
  57. package/tests/familyurl.test.ts +40 -0
  58. package/tests/posts.test.ts +76 -0
  59. package/tests/sandboxmodality.test.ts +77 -0
  60. package/tsconfig.json +22 -0
  61. package/tsconfig.node.json +22 -0
  62. package/vercel.json +3 -0
  63. package/vite.config.ts +44 -0
  64. package/vitest.config.ts +42 -0
  65. package/wrangler.toml +7 -0
  66. package/README.md +0 -3
package/App.tsx ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * App — the global anchor (layer 1 of the anchor architecture) and the only
3
+ * TSX outside the cobaia theme. cobaia is the family site: the front door
4
+ * of the DevThink family — the pricing, the support page, the app directory
5
+ * and the update blog for the ten deployable applications. The site is a
6
+ * plain CONTENT surface: no onboarding, no entry gate — the home renders
7
+ * directly and every page is public; the intro (/intro) is the one bare
8
+ * presentation page the family grammar gives every app, mounted full-bleed
9
+ * under the family fullscreen law. The root manages the theme as a single
10
+ * file: it imports only the theme anchor (sol/Sol.tsx — the file named
11
+ * after the theme folder, beside sol/sol.css), the theme stylesheet and the
12
+ * root logics, wraps the anchor in the wouter router (the subpath the pages
13
+ * deploy owns answers through the declared base, the same mount grammar
14
+ * every sibling carries) and renders the theme into the root element —
15
+ * the site never booted without this mount (the blank cobaia defect).
16
+ * Every page and component lives behind the anchor chain (App → Sol → the
17
+ * page anchors → the loose components); nothing is imported directly.
18
+ */
19
+ import { createRoot } from "react-dom/client";
20
+ import { Router as WouterRouter } from "wouter";
21
+ import Sol from "./sol/Sol";
22
+ import "@fontsource-variable/geist-mono";
23
+ import "@fontsource-variable/inter";
24
+ import "./sol/sol.css";
25
+ import { seedLabels } from "./labels";
26
+
27
+ // the copy law rides the boot: the seed refreshes the label store rows from
28
+ // the manifest (the browser cache mirrors the site db rows) before the theme
29
+ // renders — best effort, the manifest always answers the wingets.
30
+ if (typeof window !== "undefined") void seedLabels().catch(() => {});
31
+
32
+ function routerBase(): string {
33
+ // the base rides the BUILD declaration only (the sibling grammar): the
34
+ // pages subpath owns /devthink/cobaia/, and that declaration arrives
35
+ // through the vite base — deriving it from the runtime path made every
36
+ // deep link look like a deployment subfolder
37
+ const declaredBase = import.meta.env.BASE_URL;
38
+ if (declaredBase !== "/" && declaredBase !== "./") return declaredBase.replace(/\/$/, "");
39
+ return "";
40
+ }
41
+
42
+ function App() {
43
+ return (
44
+ <WouterRouter base={routerBase()}>
45
+ <Sol />
46
+ </WouterRouter>
47
+ );
48
+ }
49
+
50
+ const root = document.getElementById("root");
51
+ if (!root) throw new Error("cobaia root element is missing.");
52
+ createRoot(root).render(<App />);
package/Dockerfile ADDED
@@ -0,0 +1,48 @@
1
+ # cobaia 2.0.134 — THE SITE CONTAINER FILE
2
+ #
3
+ # The family container doctrine in the sol shape: one versioned static
4
+ # container per site. The sol theme builds inside the container — stage one
5
+ # installs the locked pnpm tree and runs the vite build, stage two serves
6
+ # the built theme through the unprivileged nginx image. The version rides
7
+ # the ARG the metadata lockstep stamps and the OCI labels declare — the
8
+ # registry lanes pass it at build time, nothing is hardcoded beside the
9
+ # stamp.
10
+
11
+ FROM node:26-alpine@sha256:40cd011b55415bc894e45b02fdd5a5dfde056921b7d23a64c74f80497af48797 AS solbuild
12
+
13
+ ARG COBAIA_VERSION=2.0.134
14
+
15
+ WORKDIR /src
16
+ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
17
+ COPY pnpmboot.package.json /tmp/pnpmboot/package.json
18
+ COPY pnpmboot.package-lock.json /tmp/pnpmboot/package-lock.json
19
+ RUN apk add --no-cache python3 make g++ \
20
+ && npm ci --prefix /tmp/pnpmboot --no-fund --no-audit \
21
+ && ln -sf /tmp/pnpmboot/node_modules/.bin/pnpm /usr/local/bin/pnpm \
22
+ && pnpm install --frozen-lockfile
23
+ COPY . .
24
+ RUN pnpm build
25
+
26
+ FROM nginxinc/nginx-unprivileged:1.29-alpine@sha256:0c79d56aee561a1d81c63f00eee5fb5fe29279560cdc55e91425133104c7fbe6
27
+
28
+ ARG COBAIA_VERSION=2.0.134
29
+
30
+ LABEL org.opencontainers.image.title="cobaia" \
31
+ org.opencontainers.image.description="cobaia — the monetization surface: the ads, the plans and the conversion, kept apart from the main sites." \
32
+ org.opencontainers.image.version="${COBAIA_VERSION}" \
33
+ org.opencontainers.image.licenses="GPL-3.0-only" \
34
+ org.opencontainers.image.source="https://github.com/wenathlan/devthink" \
35
+ org.opencontainers.image.documentation="https://github.com/wenathlan/devthink/tree/main/cobaia"
36
+
37
+ COPY --from=solbuild /src/ /usr/share/nginx/html/
38
+
39
+ # the explicit non-root runtime user (uid 101 is the nginx-unprivileged
40
+ # default): the scan gate refuses an image without a pinned unprivileged
41
+ # user, so the declaration is spelled out instead of inherited.
42
+ USER 101
43
+
44
+ # the unprivileged image answers 8080; the healthcheck probes the served
45
+ # root the same way the pages lane does.
46
+ EXPOSE 8080
47
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
48
+ CMD ["wget", "-qO-", "http://127.0.0.1:8080/"]
package/_headers ADDED
@@ -0,0 +1,4 @@
1
+ /*
2
+ X-Content-Type-Options: nosniff
3
+ X-Frame-Options: DENY
4
+ Referrer-Policy: strict-origin-when-cross-origin
package/_redirects ADDED
@@ -0,0 +1 @@
1
+ /spa/* /index.html 200
package/adsconfig.ts ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * adsconfig.ts — the honest ad slot registry of the family site, pure data
3
+ * at the app root. The family site carries ONE reserved ad container per
4
+ * content page (home, the app directory, the blog index), always at the end
5
+ * of the content, always labeled "advertisement", always a fixed aspect
6
+ * ratio so the page never shifts when a real unit arrives.
7
+ *
8
+ * THE SCRIPT RULE: no ad network script lives in this repository. The
9
+ * AdSense (or equivalent) loader is injected AFTER the network approves the
10
+ * site, through the deploy environment only. Until then the slot ids stay
11
+ * empty and the container renders the honest empty note. No network call,
12
+ * no click encouragement, no automated traffic ever touches an ad surface —
13
+ * invalid traffic is the one thing that kills both the ad account and the
14
+ * domain reputation, so the family does not do it.
15
+ */
16
+
17
+ /** one reserved ad slot: the config id and the reserved shape. */
18
+ export type adsslot = {
19
+ /** the ad network slot id — empty until the owner configures it after approval */
20
+ slotid: string;
21
+ /** the display format the reserved box mimics */
22
+ format: string;
23
+ /** the css aspect-ratio value the container reserves (never shifts layout) */
24
+ ratio: string;
25
+ };
26
+
27
+ /** the fixed label every ad container renders, visible and honest. */
28
+ export const ADS_LABEL = "advertisement";
29
+
30
+ /** the honest note the container renders while no slot id is configured. */
31
+ export const ADS_EMPTY = "ad slot not configured yet";
32
+
33
+ /** the reserved slots per content page, keyed by the page folder name. */
34
+ export const ADSSLOTS: Readonly<Record<string, adsslot>> = {
35
+ home: { slotid: "", format: "display", ratio: "728 / 90" },
36
+ apps: { slotid: "", format: "in-feed", ratio: "3 / 1" },
37
+ blog: { slotid: "", format: "in-article", ratio: "3 / 1" },
38
+ };
39
+
40
+ /**
41
+ * Reads the reserved slot of one content page.
42
+ *
43
+ * @param page the page folder name (home, apps, blog).
44
+ * @returns the slot, or undefined when the page carries no slot.
45
+ */
46
+ export function adsslotfor(page: string): adsslot | undefined {
47
+ return ADSSLOTS[page];
48
+ }
49
+
50
+ /**
51
+ * Answers whether a slot is configured with a real network id. An empty
52
+ * (or whitespace) slot id means the container stays an honest placeholder.
53
+ *
54
+ * @param slot the reserved slot entry.
55
+ * @returns true only when the slot id carries a value.
56
+ */
57
+ export function adsslotlive(slot: adsslot | undefined): boolean {
58
+ return slot !== undefined && slot.slotid.trim().length > 0;
59
+ }
package/biome.jsonc ADDED
@@ -0,0 +1,134 @@
1
+ // the family biome baseline: identical across the apps, the identity only names the app.
2
+ {
3
+ "$schema": "https://biomejs.dev/schemas/2.6.4/schema.json",
4
+ "vcs": {
5
+ "enabled": true,
6
+ "clientKind": "git",
7
+ "useIgnoreFile": false
8
+ },
9
+ "files": {
10
+ "includes": [
11
+ "**/*.ts",
12
+ "**/*.tsx",
13
+ "**/*.js",
14
+ "**/*.mjs",
15
+ "**/*.cjs",
16
+ "**/*.json",
17
+ "!**/dist",
18
+ "!**/distpackage",
19
+ "!**/target",
20
+ "!**/node_modules",
21
+ "!**/bun.lock",
22
+ "!**/*.min.js",
23
+ "!**/docs",
24
+ "!**/tests/tmp",
25
+ "!**/tests/code",
26
+ "!**/sol/dist",
27
+ "!**/sol/.vite",
28
+ "!**/sol/node_modules",
29
+ "!**/coverage",
30
+ "!**/android",
31
+ "!**/releasecheck",
32
+ "!**/build",
33
+ "!**/release",
34
+ "!index-*.js"
35
+ ]
36
+ },
37
+ "formatter": {
38
+ "enabled": true,
39
+ "indentStyle": "space",
40
+ "indentWidth": 2,
41
+ "lineWidth": 120,
42
+ "lineEnding": "lf"
43
+ },
44
+ "linter": {
45
+ "enabled": true,
46
+ "rules": {
47
+ "preset": "recommended",
48
+ "correctness": {
49
+ "noUnusedVariables": "warn",
50
+ "noUnsafeOptionalChaining": "warn",
51
+ "useExhaustiveDependencies": "warn",
52
+ "noUnusedImports": "warn",
53
+ "noUnusedPrivateClassMembers": "warn",
54
+ "noUnusedFunctionParameters": "warn",
55
+ "noConstantCondition": "warn",
56
+ "noVoidTypeReturn": "warn"
57
+ },
58
+ "a11y": {
59
+ "useButtonType": "warn",
60
+ "noStaticElementInteractions": "warn",
61
+ "useSemanticElements": "warn",
62
+ "useAriaPropsSupportedByRole": "warn"
63
+ },
64
+ "suspicious": {
65
+ "noExplicitAny": "warn",
66
+ "noArrayIndexKey": "warn",
67
+ "noControlCharactersInRegex": "warn",
68
+ "noRedeclare": "warn",
69
+ "noAssignInExpressions": "warn",
70
+ "noShadowRestrictedNames": "warn",
71
+ "noDuplicateElseIf": "warn",
72
+ "noImplicitAnyLet": "warn",
73
+ "noTsIgnore": "warn",
74
+ "noGlobalIsNan": "warn"
75
+ },
76
+ "style": {
77
+ "noNonNullAssertion": "warn",
78
+ "useTemplate": "warn",
79
+ "useExponentiationOperator": "warn",
80
+ "useConst": "warn"
81
+ },
82
+ "complexity": {
83
+ "useLiteralKeys": "warn",
84
+ "useOptionalChain": "warn",
85
+ "noUselessContinue": "warn",
86
+ "noUselessUndefinedInitialization": "warn",
87
+ "noUselessEscapeInRegex": "warn"
88
+ }
89
+ }
90
+ },
91
+ "javascript": {
92
+ "formatter": {
93
+ "quoteStyle": "double",
94
+ "semicolons": "always",
95
+ "trailingCommas": "all",
96
+ "arrowParentheses": "always"
97
+ }
98
+ },
99
+ "css": {
100
+ "parser": {
101
+ "tailwindDirectives": true
102
+ }
103
+ },
104
+ "overrides": [
105
+ {
106
+ "includes": ["**/*"],
107
+ "linter": {
108
+ "rules": {
109
+ "suspicious": {
110
+ "noDebugger": "off"
111
+ }
112
+ }
113
+ }
114
+ },
115
+ {
116
+ "includes": ["tests/workflowcheck.ts", "tests/publishlanes.test.ts"],
117
+ "linter": {
118
+ "rules": {
119
+ "suspicious": {
120
+ "noTemplateCurlyInString": "off"
121
+ }
122
+ }
123
+ }
124
+ }
125
+ ],
126
+ "assist": {
127
+ "enabled": true,
128
+ "actions": {
129
+ "source": {
130
+ "useSortedKeys": "off"
131
+ }
132
+ }
133
+ }
134
+ }