From bb14390ff9990f33a06e290341b4e869858d1fff Mon Sep 17 00:00:00 2001
From: Chris Wanstrath <2+defunkt@users.noreply.github.com>
Date: Tue, 27 Jan 2026 18:49:26 -0800
Subject: [PATCH] clock
---
.gitignore | 34 ++++-
TODO.txt | 4 +-
apps/basic/package.json | 2 +-
apps/clock/bun.lock | 37 +++++
apps/clock/index.tsx | 72 +++++++++
apps/clock/package.json | 21 +++
apps/clock/pub/digital.ttf | Bin 0 -> 24448 bytes
apps/clock/tsconfig.json | 30 ++++
apps/profile/package.json | 2 +-
apps/truisms/README.md | 9 ++
apps/truisms/bun.lock | 37 +++++
apps/truisms/index.ts | 303 +++++++++++++++++++++++++++++++++++++
apps/truisms/package.json | 20 +++
apps/truisms/tsconfig.json | 30 ++++
src/server/apps.ts | 44 +++---
15 files changed, 620 insertions(+), 25 deletions(-)
create mode 100644 apps/clock/bun.lock
create mode 100644 apps/clock/index.tsx
create mode 100644 apps/clock/package.json
create mode 100644 apps/clock/pub/digital.ttf
create mode 100644 apps/clock/tsconfig.json
create mode 100644 apps/truisms/README.md
create mode 100644 apps/truisms/bun.lock
create mode 100644 apps/truisms/index.ts
create mode 100644 apps/truisms/package.json
create mode 100644 apps/truisms/tsconfig.json
diff --git a/.gitignore b/.gitignore
index d570088..a14702c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,34 @@
-node_modules/
+# dependencies (bun install)
+node_modules
+# output
+out
+dist
+*.tgz
+
+# code coverage
+coverage
+*.lcov
+
+# logs
+logs
+_.log
+report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# caches
+.eslintcache
+.cache
+*.tsbuildinfo
+
+# IntelliJ based IDEs
+.idea
+
+# Finder (MacOS) folder config
+.DS_Store
diff --git a/TODO.txt b/TODO.txt
index e1e9730..72ab594 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -11,8 +11,8 @@
## apps
-[ ] truism
-[ ] big clock
+[x] truism
+[x] big clock
[ ] shrimp repl(?)
[ ] dungeon party
diff --git a/apps/basic/package.json b/apps/basic/package.json
index e8be2ee..0f3061a 100644
--- a/apps/basic/package.json
+++ b/apps/basic/package.json
@@ -5,7 +5,7 @@
"private": true,
"scripts": {
"toes": "bun run --watch index.tsx",
- "start": "bun run index.tsx",
+ "start": "bun toes",
"dev": "bun run --hot index.tsx"
},
"devDependencies": {
diff --git a/apps/clock/bun.lock b/apps/clock/bun.lock
new file mode 100644
index 0000000..f047791
--- /dev/null
+++ b/apps/clock/bun.lock
@@ -0,0 +1,37 @@
+{
+ "lockfileVersion": 1,
+ "workspaces": {
+ "": {
+ "name": "toes-clock",
+ "dependencies": {
+ "forge": "git+https://git.nose.space/defunkt/forge",
+ "hype": "git+https://git.nose.space/defunkt/hype",
+ },
+ "devDependencies": {
+ "@types/bun": "latest",
+ },
+ "peerDependencies": {
+ "typescript": "^5.9.2",
+ },
+ },
+ },
+ "packages": {
+ "@types/bun": ["@types/bun@1.3.7", "", { "dependencies": { "bun-types": "1.3.7" } }, "sha512-lmNuMda+Z9b7tmhA0tohwy8ZWFSnmQm1UDWXtH5r9F7wZCfkeO3Jx7wKQ1EOiKq43yHts7ky6r8SDJQWRNupkA=="],
+
+ "@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
+
+ "bun-types": ["bun-types@1.3.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-qyschsA03Qz+gou+apt6HNl6HnI+sJJLL4wLDke4iugsE6584CMupOtTY1n+2YC9nGVrEKUlTs99jjRLKgWnjQ=="],
+
+ "forge": ["forge@git+https://git.nose.space/defunkt/forge#9b6e1e91ec77d7e03589cac256d97fb9cd942184", { "peerDependencies": { "typescript": "^5" } }, "9b6e1e91ec77d7e03589cac256d97fb9cd942184"],
+
+ "hono": ["hono@4.11.7", "", {}, "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw=="],
+
+ "hype": ["hype@git+https://git.nose.space/defunkt/hype#7b9cade936c4897539d2ca14299d90f80deb6ebe", { "dependencies": { "hono": "^4.10.4", "kleur": "^4.1.5" }, "peerDependencies": { "typescript": "^5" } }, "7b9cade936c4897539d2ca14299d90f80deb6ebe"],
+
+ "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
+
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
+
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+ }
+}
diff --git a/apps/clock/index.tsx b/apps/clock/index.tsx
new file mode 100644
index 0000000..fe75314
--- /dev/null
+++ b/apps/clock/index.tsx
@@ -0,0 +1,72 @@
+import { Hype } from 'hype'
+import { define, stylesToCSS } from 'forge'
+
+const app = new Hype()
+
+const Body = define('ClockBody', {
+ base: 'body',
+ margin: 0,
+ padding: 0,
+ overflow: 'hidden',
+})
+
+const Container = define('ClockContainer', {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ height: '100vh',
+ width: '100vw',
+ background: '#000',
+})
+
+const Clock = define('Clock', {
+ fontFamily: "'DS-Digital', monospace",
+ fontSize: '12vw',
+ fontWeight: 'normal',
+ color: '#0f0',
+ textShadow: '0 0 2px rgba(0, 255, 0, 0.45)',
+ letterSpacing: '0.05em',
+ userSelect: 'none',
+})
+
+const clockScript = `
+ function updateClock() {
+ const now = new Date();
+ const hours = String(now.getHours()).padStart(2, '0');
+ const minutes = String(now.getMinutes()).padStart(2, '0');
+ const seconds = String(now.getSeconds()).padStart(2, '0');
+ document.getElementById('clock').textContent = hours + ':' + minutes + ':' + seconds;
+ }
+
+ updateClock();
+ setInterval(updateClock, 1000);
+`
+app.get('/styles.css', c => c.text(stylesToCSS(), 200, {
+ 'Content-Type': 'text/css; charset=utf-8',
+}))
+
+app.get('/', c => c.html(
+
+
+ Clock
+
+
+
+
+
+ 00:00:00
+
+
+
+
+))
+
+export default app.defaults
diff --git a/apps/clock/package.json b/apps/clock/package.json
new file mode 100644
index 0000000..3bb465e
--- /dev/null
+++ b/apps/clock/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "toes-clock",
+ "module": "src/index.ts",
+ "type": "module",
+ "private": true,
+ "scripts": {
+ "toes": "bun run --watch index.tsx",
+ "start": "bun toes",
+ "dev": "bun run --hot index.tsx"
+ },
+ "devDependencies": {
+ "@types/bun": "latest"
+ },
+ "peerDependencies": {
+ "typescript": "^5.9.2"
+ },
+ "dependencies": {
+ "hype": "git+https://git.nose.space/defunkt/hype",
+ "forge": "git+https://git.nose.space/defunkt/forge"
+ }
+}
diff --git a/apps/clock/pub/digital.ttf b/apps/clock/pub/digital.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..09258773c7219ad9e60b92b918e3c50b58f43c9e
GIT binary patch
literal 24448
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 strings.reduce((result, str, i) => result + str + (values[i] || ''), '')
+
+app.get("/", (c) => {
+ const truism = TRUISMS[Math.floor(Math.random() * TRUISMS.length)]
+ return c.html(html`
+
+
+
+ truism
+
+
+
+
+
+ ${truism}
+
+
+ `)
+})
+
+app.get("/txt", (c) => c.text(TRUISMS[Math.floor(Math.random() * TRUISMS.length)]!))
+
+export default {
+ port: process.env.PORT || 3000,
+ fetch: app.fetch,
+}
+
+const TRUISMS = [
+ "A LITTLE KNOWLEDGE CAN GO A LONG WAY",
+ "A LOT OF PROFESSIONALS ARE CRACKPOTS",
+ "A MAN CAN'T KNOW WHAT IT'S LIKE TO BE A MOTHER",
+ "A NAME MEANS A LOT JUST BY ITSELF",
+ "A POSITIVE ATTITUDE MAKES ALL THE DIFFERENCE IN THE WORLD",
+ "A RELAXED MAN IS NOT NECESSARILY A BETTER MAN",
+ "A SENSE OF TIMING IS THE MARK OF GENIUS",
+ "A SINCERE EFFORT IS ALL YOU CAN ASK",
+ "A SINGLE EVENT CAN HAVE INFINITELY MANY INTERPRETATIONS",
+ "A SOLID HOME BASE BUILDS A SENSE OF SELF",
+ "A STRONG SENSE OF DUTY IMPRISONS YOU",
+ "ABSOLUTE SUBMISSION CAN BE A FORM OF FREEDOM",
+ "ABSTRACTION IS A TYPE OF DECADENCE",
+ "ABUSE OF POWER COMES AS NO SURPRISE",
+ "ACTION CAUSES MORE TROUBLE THAN THOUGHT",
+ "ALIENATION PRODUCES ECCENTRICS OR REVOLUTIONARIES",
+ "ALL THINGS ARE DELICATELY INTERCONNECTED",
+ "AMBITION IS JUST AS DANGEROUS AS COMPLACENCY",
+ "AMBIVALENCE CAN RUIN YOUR LIFE",
+ "AN ELITE IS INEVITABLE",
+ "ANGER OR HATE CAN BE A USEFUL MOTIVATING FORCE",
+ "ANIMALISM IS PERFECTLY HEALTHY",
+ "ANY SURPLUS IS IMMORAL",
+ "ANYTHING IS A LEGITIMATE AREA OF INVESTIGATION",
+ "ARTIFICIAL DESIRES ARE DESPOILING THE EARTH",
+ "AT TIMES INACTIVITY IS PREFERABLE TO MINDLESS FUNCTIONING",
+ "AT TIMES YOUR UNCONSCIOUS IS TRUER THAN YOUR CONSCIOUS MIND",
+ "AUTOMATION IS DEADLY",
+ "AWFUL PUNISHMENT AWAITS REALLY BAD PEOPLE",
+ "BAD INTENTIONS CAN YIELD GOOD RESULTS",
+ "BEING ALONE WITH YOURSELF IS INCREASINGLY UNPOPULAR",
+ "BEING HAPPY IS MORE IMPORTANT THAN ANYTHING ELSE",
+ "BEING JUDGMENTAL IS A SIGN OF LIFE",
+ "BEING SURE OF YOURSELF MEANS YOU'RE A FOOL",
+ "BELIEVING IN REBIRTH IS THE SAME AS ADMITTING DEFEAT",
+ "BOREDOM MAKES YOU DO CRAZY THINGS",
+ "CALM IS MORE CONDUCIVE TO CREATIVITY THAN IS ANXIETY",
+ "CATEGORIZING FEAR IS CALMING",
+ "CHANGE IS VALUABLE WHEN THE OPPRESSED BECOME TYRANTS",
+ "CHASING THE NEW IS DANGEROUS TO SOCIETY",
+ "CHILDREN ARE THE HOPE OF THE FUTURE",
+ "CHILDREN ARE THE MOST CRUEL OF ALL",
+ "CLASS ACTION IS A NICE IDEA WITH NO SUBSTANCE",
+ "CLASS STRUCTURE IS AS ARTIFICIAL AS PLASTIC",
+ "CONFUSING YOURSELF IS A WAY TO STAY HONEST",
+ "CRIME AGAINST PROPERTY IS RELATIVELY UNIMPORTANT",
+ "DECADENCE CAN BE AN END IN ITSELF",
+ "DECENCY IS A RELATIVE THING",
+ "DEPENDENCE CAN BE A MEAL TICKET",
+ "DESCRIPTION IS MORE VALUABLE THAN METAPHOR",
+ "DEVIANTS ARE SACRIFICED TO INCREASE GROUP SOLIDARITY",
+ "DISGUST IS THE APPROPRIATE RESPONSE TO MOST SITUATIONS",
+ "DISORGANIZATION IS A KIND OF ANESTHESIA",
+ "DON'T PLACE TOO MUCH TRUST IN EXPERTS",
+ "DRAMA OFTEN OBSCURES THE REAL ISSUES",
+ "DREAMING WHILE AWAKE IS A FRIGHTENING CONTRADICTION",
+ "DYING AND COMING BACK GIVES YOU CONSIDERABLE PERSPECTIVE",
+ "DYING SHOULD BE AS EASY AS FALLING OFF A LOG",
+ "EATING TOO MUCH IS CRIMINAL",
+ "ELABORATION IS A FORM OF POLLUTION",
+ "EMOTIONAL RESPONSES ARE AS VALUABLE AS INTELLECTUAL RESPONSES",
+ "ENJOY YOURSELF BECAUSE YOU CAN'T CHANGE ANYTHING ANYWAY",
+ "ENSURE THAT YOUR LIFE STAYS IN FLUX",
+ "EVEN YOUR FAMILY CAN BETRAY YOU",
+ "EVERY ACHIEVEMENT REQUIRES A SACRIFICE",
+ "EVERYONE'S WORK IS EQUALLY IMPORTANT",
+ "EVERYTHING THAT'S INTERESTING IS NEW",
+ "EXCEPTIONAL PEOPLE DESERVE SPECIAL CONCESSIONS",
+ "EXPIRING FOR LOVE IS BEAUTIFUL BUT STUPID",
+ "EXPRESSING ANGER IS NECESSARY",
+ "EXTREME BEHAVIOR HAS ITS BASIS IN PATHOLOGICAL PSYCHOLOGY",
+ "EXTREME SELF-CONSCIOUSNESS LEADS TO PERVERSION",
+ "FAITHFULNESS IS A SOCIAL NOT A BIOLOGICAL LAW",
+ "FAKE OR REAL INDIFFERENCE IS A POWERFUL PERSONAL WEAPON",
+ "FATHERS OFTEN USE TOO MUCH FORCE",
+ "FEAR IS THE GREATEST INCAPACITATOR",
+ "FREEDOM IS A LUXURY NOT A NECESSITY",
+ "GIVING FREE REIN TO YOUR EMOTIONS IS AN HONEST WAY TO LIVE",
+ "GO ALL OUT IN ROMANCE AND LET THE CHIPS FALL WHERE THEY MAY",
+ "GOING WITH THE FLOW IS SOOTHING BUT RISKY",
+ "GOOD DEEDS EVENTUALLY ARE REWARDED",
+ "GOVERNMENT IS A BURDEN ON THE PEOPLE",
+ "GRASS ROOTS AGITATION IS THE ONLY HOPE",
+ "GUILT AND SELF-LACERATION ARE INDULGENCES",
+ "HABITUAL CONTEMPT DOESN'T REFLECT A FINER SENSIBILITY",
+ "HIDING YOUR MOTIVES IS DESPICABLE",
+ "HOLDING BACK PROTECTS YOUR VITAL ENERGIES",
+ "HUMANISM IS OBSOLETE",
+ "HUMOR IS A RELEASE",
+ "IDEALS ARE REPLACED BY CONVENTIONAL GOALS AT A CERTAIN AGE",
+ "IF YOU AREN'T POLITICAL YOUR PERSONAL LIFE SHOULD BE EXEMPLARY",
+ "IF YOU CAN'T LEAVE YOUR MARK GIVE UP",
+ "IF YOU HAVE MANY DESIRES YOUR LIFE WILL BE INTERESTING",
+ "IF YOU LIVE SIMPLY THERE IS NOTHING TO WORRY ABOUT",
+ "IGNORING ENEMIES IS THE BEST WAY TO FIGHT",
+ "ILLNESS IS A STATE OF MIND",
+ "IMPOSING ORDER IS MAN'S VOCATION FOR CHAOS IS HELL",
+ "IN SOME INSTANCES IT'S BETTER TO DIE THAN TO CONTINUE",
+ "INHERITANCE MUST BE ABOLISHED",
+ "IT CAN BE HELPFUL TO KEEP GOING NO MATTER WHAT",
+ "IT IS HEROIC TO TRY TO STOP TIME",
+ "IT IS MAN'S FATE TO OUTSMART HIMSELF",
+ "IT'S A GIFT TO THE WORLD NOT TO HAVE BABIES",
+ "IT'S BETTER TO BE A GOOD PERSON THAN A FAMOUS PERSON",
+ "IT'S BETTER TO BE LONELY THAN TO BE WITH INFERIOR PEOPLE",
+ "IT'S BETTER TO BE NAIVE THAN JADED",
+ "IT'S BETTER TO STUDY THE LIVING FACT THAN TO ANALYZE HISTORY",
+ "IT'S CRUCIAL TO HAVE AN ACTIVE FANTASY LIFE",
+ "IT'S GOOD TO GIVE EXTRA MONEY TO CHARITY",
+ "IT'S IMPORTANT TO STAY CLEAN ON ALL LEVELS",
+ "IT'S JUST AN ACCIDENT THAT YOUR PARENTS ARE YOUR PARENTS",
+ "IT'S NOT GOOD TO HOLD TOO MANY ABSOLUTES",
+ "IT'S NOT GOOD TO OPERATE ON CREDIT",
+ "IT'S VITAL TO LIVE IN HARMONY WITH NATURE",
+ "JUST BELIEVING SOMETHING CAN MAKE IT HAPPEN",
+ "KEEP SOMETHING IN RESERVE FOR EMERGENCIES",
+ "KILLING IS UNAVOIDABLE BUT IS NOTHING TO BE PROUD OF",
+ "KNOWING YOURSELF LETS YOU UNDERSTAND OTHERS",
+ "KNOWLEDGE SHOULD BE ADVANCED AT ALL COSTS",
+ "LABOR IS A LIFE-DESTROYING ACTIVITY",
+ "LACK OF CHARISMA CAN BE FATAL",
+ "LEISURE TIME IS A GIGANTIC SMOKE SCREEN",
+ "LISTEN WHEN YOUR BODY TALKS",
+ "LOOKING BACK IS THE FIRST SIGN OF AGING AND DECAY",
+ "LOVING ANIMALS IS A SUBSTITUTE ACTIVITY",
+ "LOW EXPECTATIONS ARE GOOD PROTECTION",
+ "MANUAL LABOR CAN BE REFRESHING AND WHOLESOME",
+ "MEN ARE NOT MONOGAMOUS BY NATURE",
+ "MODERATION KILLS THE SPIRIT",
+ "MONEY CREATES TASTE",
+ "MONOMANIA IS A PREREQUISITE OF SUCCESS",
+ "MORALS ARE FOR LITTLE PEOPLE",
+ "MOST PEOPLE ARE NOT FIT TO RULE THEMSELVES",
+ "MOSTLY YOU SHOULD MIND YOUR OWN BUSINESS",
+ "MOTHERS SHOULDN'T MAKE TOO MANY SACRIFICES",
+ "MUCH WAS DECIDED BEFORE YOU WERE BORN",
+ "MURDER HAS ITS SEXUAL SIDE",
+ "MYTHS CAN MAKE REALITY MORE INTELLIGIBLE",
+ "NOISE CAN BE HOSTILE",
+ "NOTHING UPSETS THE BALANCE OF GOOD AND EVIL",
+ "OCCASIONALLY PRINCIPLES ARE MORE VALUABLE THAN PEOPLE",
+ "OFFER VERY LITTLE INFORMATION ABOUT YOURSELF",
+ "OFTEN YOU SHOULD ACT LIKE YOU ARE SEXLESS",
+ "OLD FRIENDS ARE BETTER LEFT IN THE PAST",
+ "OPACITY IS AN IRRESISTIBLE CHALLENGE",
+ "PAIN CAN BE A VERY POSITIVE THING",
+ "PEOPLE ARE BORING UNLESS THEY'RE EXTREMISTS",
+ "PEOPLE ARE NUTS IF THEY THINK THEY ARE IMPORTANT",
+ "PEOPLE ARE RESPONSIBLE FOR WHAT THEY DO UNLESS THEY'RE INSANE",
+ "PEOPLE WHO DON'T WORK WITH THEIR HANDS ARE PARASITES",
+ "PEOPLE WHO GO CRAZY ARE TOO SENSITIVE",
+ "PEOPLE WON'T BEHAVE IF THEY HAVE NOTHING TO LOSE",
+ "PHYSICAL CULTURE IS SECOND-BEST",
+ "PLANNING FOR THE FUTURE IS ESCAPISM",
+ "PLAYING IT SAFE CAN CAUSE A LOT OF DAMAGE IN THE LONG RUN",
+ "POLITICS IS USED FOR PERSONAL GAIN",
+ "POTENTIAL COUNTS FOR NOTHING UNTIL IT'S REALIZED",
+ "PRIVATE PROPERTY CREATED CRIME",
+ "PURSUING PLEASURE FOR THE SAKE OF PLEASURE WILL RUIN YOU",
+ "PUSH YOURSELF TO THE LIMIT AS OFTEN AS POSSIBLE",
+ "RAISE BOYS AND GIRLS THE SAME WAY",
+ "RANDOM MATING IS GOOD FOR DEBUNKING SEX MYTHS",
+ "RECHANNELING DESTRUCTIVE IMPULSES IS A SIGN OF MATURITY",
+ "RECLUSES ALWAYS GET WEAK",
+ "REDISTRIBUTING WEALTH IS IMPERATIVE",
+ "RELATIVITY IS NO BOON TO MANKIND",
+ "RELIGION CAUSES AS MANY PROBLEMS AS IT SOLVES",
+ "REMEMBER YOU ALWAYS HAVE FREEDOM OF CHOICE",
+ "REPETITION IS THE BEST WAY TO LEARN",
+ "RESOLUTIONS SERVE TO EASE YOUR CONSCIENCE",
+ "REVOLUTION BEGINS WITH CHANGES IN THE INDIVIDUAL",
+ "ROMANTIC LOVE WAS INVENTED TO MANIPULATE WOMEN",
+ "ROUTINE IS A LINK WITH THE PAST",
+ "ROUTINE SMALL EXCESSES ARE WORSE THAN THE OCCASIONAL DEBAUCH",
+ "SACRIFICING YOURSELF FOR A BAD CAUSE IS NOT A MORAL ACT",
+ "SALVATION CAN'T BE BOUGHT AND SOLD",
+ "SELF-AWARENESS CAN BE CRIPPLING",
+ "SELF-CONTEMPT CAN DO MORE HARM THAN GOOD",
+ "SELFISHNESS IS THE MOST BASIC MOTIVATION",
+ "SELFLESSNESS IS THE HIGHEST ACHIEVEMENT",
+ "SEPARATISM IS THE WAY TO A NEW BEGINNING",
+ "SEX DIFFERENCES ARE HERE TO STAY",
+ "SIN IS A MEANS OF SOCIAL CONTROL",
+ "SLIPPING INTO MADNESS IS GOOD FOR THE SAKE OF COMPARISON",
+ "SLOPPY THINKING GETS WORSE OVER TIME",
+ "SOLITUDE IS ENRICHING",
+ "SOMETIMES SCIENCE ADVANCES FASTER THAN IT SHOULD",
+ "SOMETIMES THINGS SEEM TO HAPPEN OF THEIR OWN ACCORD",
+ "SPENDING TOO MUCH TIME ON SELF-IMPROVEMENT IS ANTISOCIAL",
+ "STARVATION IS NATURE'S WAY",
+ "STASIS IS A DREAM STATE",
+ "STERILIZATION IS A WEAPON OF THE RULERS",
+ "STRONG EMOTIONAL ATTACHMENT STEMS FROM BASIC INSECURITY",
+ "STUPID PEOPLE SHOULDN'T BREED",
+ "SURVIVAL OF THE FITTEST APPLIES TO MEN AND ANIMALS",
+ "SYMBOLS ARE MORE MEANINGFUL THAN THINGS THEMSELVES",
+ "TAKING A STRONG STAND PUBLICIZES THE OPPOSITE POSITION",
+ "TALKING IS USED TO HIDE ONE'S INABILITY TO ACT",
+ "TEASING PEOPLE SEXUALLY CAN HAVE UGLY CONSEQUENCES",
+ "TECHNOLOGY WILL MAKE OR BREAK US",
+ "THE CRUELEST DISAPPOINTMENT IS WHEN YOU LET YOURSELF DOWN",
+ "THE DESIRE TO REPRODUCE IS A DEATH WISH",
+ "THE FAMILY IS LIVING ON BORROWED TIME",
+ "THE IDEA OF REVOLUTION IS AN ADOLESCENT FANTASY",
+ "THE IDEA OF TRANSCENDENCE IS USED TO OBSCURE OPPRESSION",
+ "THE IDIOSYNCRATIC HAS LOST ITS AUTHORITY",
+ "THE MOST PROFOUND THINGS ARE INEXPRESSIBLE",
+ "THE MUNDANE IS TO BE CHERISHED",
+ "THE NEW IS NOTHING BUT A RESTATEMENT OF THE OLD",
+ "THE ONLY WAY TO BE PURE IS TO STAY BY YOURSELF",
+ "THE SUM OF YOUR ACTIONS DETERMINES WHAT YOU ARE",
+ "THE UNATTAINABLE IS INVARIABLY ATTRACTIVE",
+ "THE WORLD OPERATES ACCORDING TO DISCOVERABLE LAWS",
+ "THERE ARE TOO FEW IMMUTABLE TRUTHS TODAY",
+ "THERE'S NOTHING EXCEPT WHAT YOU SENSE",
+ "THERE'S NOTHING REDEEMING IN TOIL",
+ "THINKING TOO MUCH CAN ONLY CAUSE PROBLEMS",
+ "THREATENING SOMEONE SEXUALLY IS A HORRIBLE ACT",
+ "TIMIDITY IS LAUGHABLE",
+ "TO DISAGREE PRESUPPOSES MORAL INTEGRITY",
+ "TO VOLUNTEER IS REACTIONARY",
+ "TORTURE IS BARBARIC",
+ "TRADING A LIFE FOR A LIFE IS FAIR ENOUGH",
+ "TRUE FREEDOM IS FRIGHTFUL",
+ "UNIQUE THINGS MUST BE THE MOST VALUABLE",
+ "UNQUESTIONING LOVE DEMONSTRATES LARGESSE OF SPIRIT",
+ "USING FORCE TO STOP FORCE IS ABSURD",
+ "VIOLENCE IS PERMISSIBLE EVEN DESIRABLE OCCASIONALLY",
+ "WAR IS A PURIFICATION RITE",
+ "WE MUST MAKE SACRIFICES TO MAINTAIN OUR QUALITY OF LIFE",
+ "WHEN SOMETHING TERRIBLE HAPPENS PEOPLE WAKE UP",
+ "WISHING THINGS AWAY IS NOT EFFECTIVE",
+ "WITH PERSEVERANCE YOU CAN DISCOVER ANY TRUTH",
+ "WORDS TEND TO BE INADEQUATE",
+ "WORRYING CAN HELP YOU PREPARE",
+ "YOU ARE A VICTIM OF THE RULES YOU LIVE BY",
+ "YOU ARE GUILELESS IN YOUR DREAMS",
+ "YOU ARE RESPONSIBLE FOR CONSTITUTING MEANING",
+ "YOU ARE THE PAST PRESENT AND FUTURE",
+ "YOU CAN LIVE ON THROUGH YOUR DESCENDANTS",
+ "YOU CAN'T EXPECT PEOPLE TO BE SOMETHING THEY'RE NOT",
+ "YOU CAN'T FOOL OTHERS IF YOU'RE FOOLING YOURSELF",
+ "YOU DON'T KNOW WHAT'S WHAT UNTIL YOU SUPPORT YOURSELF",
+ "YOU HAVE TO HURT OTHERS TO BE EXTRAORDINARY",
+ "YOU MUST BE INTIMATE WITH A TOKEN FEW",
+ "YOU MUST DISAGREE WITH AUTHORITY FIGURES",
+ "YOU MUST HAVE ONE GRAND PASSION",
+ "YOU MUST KNOW WHERE YOU STOP AND THE WORLD BEGINS",
+ "YOU ONLY CAN UNDERSTAND SOMEONE OF YOUR OWN SEX",
+ "YOU OWE THE WORLD NOT THE OTHER WAY AROUND",
+ "YOU SHOULD STUDY AS MUCH AS POSSIBLE",
+ "YOUR ACTIONS ARE POINTLESS IF NO ONE NOTICES",
+ "YOUR OLDEST FEARS ARE THE WORST ONES"
+]
diff --git a/apps/truisms/package.json b/apps/truisms/package.json
new file mode 100644
index 0000000..b13aab3
--- /dev/null
+++ b/apps/truisms/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "toes-app",
+ "module": "src/index.ts",
+ "type": "module",
+ "private": true,
+ "scripts": {
+ "toes": "bun run --watch index.ts",
+ "dev": "bun run --hot index.ts"
+ },
+ "devDependencies": {
+ "@types/bun": "latest"
+ },
+ "peerDependencies": {
+ "typescript": "^5.9.2"
+ },
+ "dependencies": {
+ "hype": "git+https://git.nose.space/defunkt/hype",
+ "forge": "git+https://git.nose.space/defunkt/forge"
+ }
+}
diff --git a/apps/truisms/tsconfig.json b/apps/truisms/tsconfig.json
new file mode 100644
index 0000000..545396c
--- /dev/null
+++ b/apps/truisms/tsconfig.json
@@ -0,0 +1,30 @@
+{
+ "compilerOptions": {
+ // Environment setup & latest features
+ "lib": ["ESNext"],
+ "target": "ESNext",
+ "module": "Preserve",
+ "moduleDetection": "force",
+ "jsx": "react-jsx",
+ "jsxImportSource": "hono/jsx",
+ "allowJs": true,
+
+ // Bundler mode
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "noEmit": true,
+
+ // Best practices
+ "strict": true,
+ "skipLibCheck": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedIndexedAccess": true,
+ "noImplicitOverride": true,
+
+ // Some stricter flags (disabled by default)
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noPropertyAccessFromIndexSignature": false
+ }
+}
diff --git a/src/server/apps.ts b/src/server/apps.ts
index f992191..5e290b5 100644
--- a/src/server/apps.ts
+++ b/src/server/apps.ts
@@ -40,25 +40,24 @@ export const runApps = () => {
}
}
-const isApp = (dir: string): boolean => {
- try {
- const file = readFileSync(join(APPS_DIR, dir, 'package.json'), 'utf-8')
- const json = JSON.parse(file)
- return !!json.scripts?.toes
- } catch (e) {
- return false
- }
-}
+const isApp = (dir: string): boolean =>
+ Object.values(loadApp(dir)).length > 0
const loadApp = (dir: string) => {
try {
const file = readFileSync(join(APPS_DIR, dir, 'package.json'), 'utf-8')
- const json = JSON.parse(file)
- if (json.scripts?.toes) {
- return json
- } else {
- err(dir, 'No `bun toes` script in package.json')
+ try {
+ const json = JSON.parse(file)
+
+ if (json.scripts?.toes) {
+ return json
+ } else {
+ err(dir, 'No `bun toes` script in package.json')
+ return {}
+ }
+ } catch (e) {
+ err(dir, 'Invalid JSON in package.json:', e instanceof Error ? e.message : String(e))
return {}
}
} catch (e) {
@@ -135,17 +134,22 @@ const stopApp = (dir: string) => {
}
const watchAppsDir = () => {
- watch(APPS_DIR, (event, filename) => {
- console.log('apps dir')
+ watch(APPS_DIR, { recursive: true }, (event, filename) => {
if (!filename) return
- if (isApp(filename) && !_runningApps.has(filename)) {
+ // Only care about package.json changes
+ if (!filename.endsWith('package.json')) return
+
+ // Extract the app directory name from the path (e.g., "myapp/package.json" -> "myapp")
+ const dir = filename.split('/')[0]!
+
+ if (isApp(dir) && !_runningApps.has(dir)) {
const port = getPort()
- runApp(filename, port)
+ runApp(dir, port)
}
- if (_runningApps.has(filename) && !isApp(filename))
- stopApp(filename)
+ if (_runningApps.has(dir) && !isApp(dir))
+ stopApp(dir)
})
}