feat: init

This commit is contained in:
Steve
2025-09-29 09:21:47 -04:00
commit 0957a831a1
23 changed files with 523 additions and 0 deletions

38
.gitignore vendored Normal file
View File

@@ -0,0 +1,38 @@
# dependencies (bun install)
node_modules
# output
out
dist
site-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
# Misc
Notes.md

107
CLAUDE.md Normal file
View File

@@ -0,0 +1,107 @@
---
Default to using Bun instead of Node.js.
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
- Use `bun test` instead of `jest` or `vitest`
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
- Bun automatically loads .env, so don't use dotenv.
## APIs
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
- `Bun.redis` for Redis. Don't use `ioredis`.
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
- `WebSocket` is built-in. Don't use `ws`.
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
- Bun.$`ls` instead of execa.
## Testing
Use `bun test` to run tests.
```ts#index.test.ts
import { test, expect } from "bun:test";
test("hello world", () => {
expect(1).toBe(1);
});
```
## Frontend
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
Server:
```ts#index.ts
import index from "./index.html"
Bun.serve({
routes: {
"/": index,
"/api/users/:id": {
GET: (req) => {
return new Response(JSON.stringify({ id: req.params.id }));
},
},
},
// optional websocket support
websocket: {
open: (ws) => {
ws.send("Hello, world!");
},
message: (ws, message) => {
ws.send(message);
},
close: (ws) => {
// handle close
}
},
development: {
hmr: true,
console: true,
}
})
```
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
```html#index.html
<html>
<body>
<h1>Hello, world!</h1>
<script type="module" src="./frontend.tsx"></script>
</body>
</html>
```
With the following `frontend.tsx`:
```tsx#frontend.tsx
import React from "react";
// import .css files directly and it works
import './index.css';
import { createRoot } from "react-dom/client";
const root = createRoot(document.body);
export default function Frontend() {
return <h1>Hello, world!</h1>;
}
root.render(<Frontend />);
```
Then, run index.ts
```sh
bun --hot ./index.ts
```
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Steve Simkins
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

67
README.md Normal file
View File

@@ -0,0 +1,67 @@
# Norns
![cover](site/og.png)
Interoperable web components for decentralized applications
## Overview and Reasoning
Some of the first crypto apps we build were in React, and it's possible we might be able to resurrect some of them if we tried. However the unfortunate reality is that web dev frameworks accelerate at an alarming rate, and that goes for blockchain related libraries as well (shudders at the memory of viem v1 -> v2 and ethers v5 -> v6). It doesn't have to be like that though.
Web components are independant pieces of Javascript that can be imported to plain HTML but also frameworks as well. They're atomic, existing on their own and able to out-last any framework as long as we keep using Javscript (unfortunately I think that is the case). Some notable existing web component libraries include [Material Web](https://github.com/material-components/material-web) and [Web Awesome](https://github.com/shoelace-style/webawesome).
The goal of Norns is to provide the Ethereum ecosystem a set of simple yet powerful web components for building decentralized applications. The advantage we have today is that we've experienced good DX from modern frameworks, so we have the ability to build components that feel familiar to devs building UIs for smart contracts. We will start small but slowly grow the offering as we get a better feel for what devs need; check out the [Roadmap](#roadmap) for more information.
## Local Development Setup
1. Clone and install dependencies with [Bun](https://bun.sh)
```bash
git clone https://github.com/stevedylandev/norns
cd norns
bun install
```
2. Run the dev server
```bash
bun dev
```
This will run a simple server for `site/index.html` which imports components from `src/component/`
3. Build
After editing components and testing them in the dev server you can run the `build` command to generate the CLI from `src/index.ts` that will create a `dist` folder. This enables users to run something like `npx norns-ui@latest init` to setup a project and add components, similar to shadcn/ui.
## Roadmap
Still figuring this out, suggestions and examples welcome!
**CLI**
- [x] Implement `norns.json` initialization
- [x] Improve styles and UX of commands and help menus
- [ ] Include utility files like types in initialization and `norns.json`
**Components**
- [x] Connect Wallet
- [x] Contract Call
- [ ] TX Toasts?
- [ ] Contract State (similar to contract call but auto loads the state)
- [ ] Framework compatability
- [x] React
- [ ] Svelte
- [ ] Vue
- [ ] General types through JSDoc?
- [ ] Add tailwindcss class/className prop
- [ ] Styles override if shadcn present?
## Contributing
Norns is still in early development but definitely open to contributions! Just open an issue to get the ball rolling :)
## Contact
Feel free to reach out to any of my [socials](https://stevedylan.dev/links) or [shoot me an email](mailto:contact@stevedylan.dev)

37
biome.json Normal file
View File

@@ -0,0 +1,37 @@
{
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"useArrowFunction": "off"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

40
bun.lock Normal file
View File

@@ -0,0 +1,40 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "norns-ui",
"dependencies": {
"@noble/hashes": "^2.0.0",
},
"devDependencies": {
"@types/bun": "latest",
"bun-plugin-tailwind": "^0.0.15",
"tailwindcss": "^4.1.13",
},
"peerDependencies": {
"typescript": "^5",
},
},
},
"packages": {
"@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="],
"@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
"@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="],
"@types/react": ["@types/react@19.1.14", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q=="],
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.0.15", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-qtAXMNGG4R0UGGI8zWrqm2B7BdXqx48vunJXBPzfDOHPA5WkRUZdTSbE7TFwO4jLhYqSE23YMWsM9NhE6ovobw=="],
"bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"tailwindcss": ["tailwindcss@4.1.13", "", {}, "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w=="],
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
}
}

2
bunfig.toml Normal file
View File

@@ -0,0 +1,2 @@
[serve.static]
plugins = ["bun-plugin-tailwind"]

6
orbiter.json Normal file
View File

@@ -0,0 +1,6 @@
{
"siteId": "e4400933-efdd-4788-80d9-6a28872c3dc7",
"domain": "norns",
"buildCommand": "bun run build:site",
"buildDir": "site-dist"
}

18
package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "blogfeeds",
"version": "0.0.1",
"description": "Webstie to promote blogfeeds",
"type": "module",
"scripts": {
"build": "bun scripts/build-site",
"dev": "bun site/index.html --console"
},
"devDependencies": {
"@types/bun": "latest",
"bun-plugin-tailwind": "^0.0.15",
"tailwindcss": "^4.1.13"
},
"peerDependencies": {
"typescript": "^5"
}
}

18
scripts/build-site.ts Normal file
View File

@@ -0,0 +1,18 @@
import { $ } from "bun";
// Clean up dist
await $`rm -rf site-dist`;
// Create new dist
await $`cp -r site site-dist`;
// Compile tailwindcss
await $`bunx @tailwindcss/cli -i ./site/input.css -o ./site-dist/output.css `;
// Read index file
const htmlContent = await Bun.file("site-dist/index.html").text();
// Update script tags and css link
const updatedHtml = htmlContent
.replace(
`<link rel="stylesheet" href="tailwindcss" />`,
`<link rel="stylesheet" href="output.css" />`,
);
// Write file
await Bun.write("site-dist/index.html", updatedHtml);

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
site/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
site/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

BIN
site/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
site/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

3
site/github.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>

After

Width:  |  Height:  |  Size: 832 B

131
site/index.html Normal file
View File

@@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blog Feeds</title>
<link rel="stylesheet" href="tailwindcss" />
<meta name="description" content="">
<!-- Facebook Meta Tags -->
<meta property="og:url" content="">
<meta property="og:type" content="website">
<meta property="og:title" content="Norns">
<meta property="og:description" content="">
<meta property="og:image" content="/og.png">
<!-- Twitter Meta Tags -->
<meta name="twitter:card" content="summary_large_image">
<meta property="twitter:domain" content="">
<meta property="twitter:url" content="">
<meta name="twitter:title" content="">
<meta name="twitter:description" content="">
<meta name="twitter:image" content="/og.png">
</head>
<body class="overflow-x-auto">
<div class="flex snap-x snap-mandatory overflow-x-scroll">
<section class="min-h-screen w-screen flex-shrink-0 flex flex-col gap-12 justify-center items-center px-8 snap-center">
<div class="max-w-4xl space-y-4">
<p>Do you kinda hate social media?</p>
<p>Tired of doom scrolling through addicting feeds?</p>
<p>Miss the days when the web was just about connecting with people and their thoughts or ideas?</p>
<p>We believe there's an answer to that problem, and it's called</p>
<h1 class="text-7xl font-bold mt-24">Blog Feeds</h1>
</div>
</section>
<section class="min-h-screen w-screen flex-shrink-0 flex flex-col justify-center items-center px-8 snap-center">
<div class="max-w-xl space-y-4">
<p>No this isn't another platform.</p>
<p>
You don't sign up here.
</p>
<p>You just write what's on your mind, respond to an idea, post a recipe, or share a photo. It's blogs, but perhaps not in the way you would think of blogs.</p>
<p>It just takes three things to participate:</p>
<ol class="list-disc pl-4 text-2xl font-bold">
<li>Blog</li>
<li>RSS</li>
<li>Feeds</li>
</ol>
</div>
</section>
<section class="min-h-screen w-screen flex-shrink-0 flex flex-col justify-center items-center px-8 snap-center">
<div class="max-w-xl space-y-4">
<h2 class="text-4xl font-bold">Blog</h2>
<p>Let me reassure you, this is actually simpler than what you're probably thinking. This doesn't have to be some well polished highly viewed monetization machine. It's just a simple website where you can casually talk about whatever you want to talk about! It can be long, short, a list of small things, or just a quote. It should be how you talk with other people in your own life, or how you communicate with the outside world. It should be you on a page. Here's a few places you can make a blog that are RSS enabled:</p>
<p><strong>Hosted Services</strong></p>
<p>These are great if you are not quite a technical person and need everything to be simple and easy to use.</p>
<ul class="list-disc pl-4 text-md font-semibold">
<li>Bear Blog ⭐️</li>
<li>Substack</li>
<li>Ghost</li>
<li>Wordpress</li>
<li>Squarespace</li>
</ul>
<p><strong>Self Hosted Frameworks</strong></p>
<p>For the devs 🫡 Some of my favorite frameworks and tools for making a blog!</p>
<ul>
<li>Astro</li>
<li>Hugo</li>
<li>Zola</li>
<li>11ty</li>
<li>Jekyll</li>
</ul>
</div>
</section>
<section class="min-h-screen w-screen flex-shrink-0 flex flex-col justify-center items-center px-8 snap-center">
<div class="max-w-xl space-y-4">
<h2 class="text-4xl font-bold">RSS</h2>
<p>Also known as "really simple syndication", RSS has been around for decades. In short, it's simply a list of data that can be updated and notify people when something new has been posted. This is great for blogs since you don't have to always check all the people you want to follow. When you make your blog, you want to make sure it's RSS enabled (resources are further down). You also will want to get an RSS reader, as this will allow you to create a list of people to follow and let you read posts as they are published. These can vary from hosted web platforms where you might need to pay at some point to just free apps you can download. Would highly recommend trying a few and seeing which works best!</p>
<ul>
<li>Feedly</li>
<li>Inoreader</li>
<li>NetNewsWire</li>
<li>Feeeeed</li>
<li><a href="https://apps.apple.com/app/id6475002485">https://apps.apple.com/app/id6475002485</a></li>
</ul>
</div>
</section>
<section class="min-h-screen w-screen flex-shrink-0 flex flex-col justify-center items-center px-8 snap-center">
<div class="max-w-xl space-y-4">
<h2 class="text-4xl font-bold">Feeds</h2>
<p>This takes us to our final point: Feeds. You can probably get away with just the first two items and then sharing it with people you already know, but what about meeting or talking to people you don't know? That's where Feeds come in. The idea is to create another page on your blog that has all the RSS feeds you're subscribed to. By keeping this public and always up to date, someone can visit your page, find someone new and follow them. Perhaps that person also has a feeds page, and the cycle continues until there is a natural and organic network of people all sharing with each other. So if you have a blog, consider making a feeds page and sharing it! If your RSS reader supports OPML file exports and imports, perhaps you can share that file as well to make it easier to share your feeds.</p>
</div>
</section>
<section class="min-h-screen w-screen flex-shrink-0 flex flex-col justify-center items-center px-8 snap-center">
<div class="max-w-xl space-y-4">
<p>The best part about blog feeds? It's just an idea. There's no central authority. There's no platform. No massive tech giant trying to take your data. It's just you and the people you care about.</p>
</div>
</section>
<section class="min-h-screen w-screen flex-shrink-0 flex flex-col justify-center items-center px-8 snap-center">
<div class="max-w-xl space-y-4">
<h2 class="text-4xl font-bold">FAQ</h2>
<ul>
<li>Where do I sign up?</li>
<li>How much does Blog Feeds cost?</li>
<li>Does this replace social media?</li>
<li>What about monetization?</li>
<li>What about comments or dialogue?</li>
</ul>
</div>
</section>
</div>
</body>
</html>

1
site/input.css Normal file
View File

@@ -0,0 +1 @@
@import "tailwindcss";

4
site/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

BIN
site/og.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

1
site/site.webmanifest Normal file
View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

29
tsconfig.json Normal file
View File

@@ -0,0 +1,29 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"jsx": "react-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
}
}