Skip to content

Commit a665242

Browse files
committed
Basic chat implementation
This is not yet a real time chat implementation, but it works. The chat creator is an actor that can create new chats. A chat is an entity that offers the affordance of creating a participation. A participation is like an invite link that's associated with a name. Anyone who has a participation URL can submit messages under that name. I've also changed some various things in the actor framework. It's still a work in progress and this is the first real feature that involves multiple participants and so on. The next thing to add is real-time updates which I'm still thinking about. I'll write about that in a note.
1 parent 60c11e2 commit a665242

17 files changed

Lines changed: 891 additions & 373 deletions

File tree

notes/Node.Town/ActivityPub.md

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
# Integration
22

3-
ActivityPub is a decentralized social networking protocol that enables
4-
different systems to communicate and share activities in a standardized way.
5-
It's particularly relevant for Bubble because:
3+
ActivityPub is a decentralized social networking protocol that enables different systems to communicate and share activities in a standardized way. It's particularly relevant for Bubble because:
64

7-
1. It aligns with our distributed "froth" architecture where bubbles need to
8-
interact
5+
1. It aligns with our distributed "froth" architecture where bubbles need to interact
96
2. It provides a standard way to handle actor identities and capabilities
107
3. It fits naturally with our existing RDF/semantic foundation
118

@@ -40,9 +37,7 @@ First of all, we need some notion of [[User Identity]] and [[Authentication]].
4037

4138
# [[ActivityStreams]] Ontology
4239

43-
ActivityStreams 2.0 defines a structured, RDF-compatible JSON-LD vocabulary
44-
for describing social activities, actors, and objects. Core classes and
45-
properties include:
40+
ActivityStreams 2.0 defines a structured, RDF-compatible JSON-LD vocabulary for describing social activities, actors, and objects. Core classes and properties include:
4641

4742
## Classes
4843

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
This chat system reimagines how people can communicate online by building entirely on capability-based security principles instead of traditional user accounts. When someone creates a chat room, they receive a special URL that grants them the power to create "participations." Each participation manifests as a unique URL that enables sending messages under a specific name in that chat.
2+
3+
The system works through simple URL sharing rather than accounts and passwords. To add someone to a chat, the creator generates a participation URL for a chosen name and shares it through any preferred channel. Anyone with that URL can then send messages appearing under that name, and if multiple people have the same URL, they'll all appear as that participant. This eliminates the need for user authentication while maintaining a clean way to manage who can speak and under what names.
4+
5+
The design extends naturally to delegation of capabilities. Chat creators can generate delegate URLs that grant others limited power to create new participations. These delegate capabilities can be constrained in various ways - allowing someone to add only a specific number of participants, create temporary participations that expire, or add participants matching certain name patterns. All security and permissions are embedded in the URLs themselves, eliminating the need for complex role management systems.
6+
7+
This approach aligns with REST architectural principles and capability-based security models, where possessing a URL grants specific, well-defined powers that can be delegated and constrained flexibly. The result is a simple yet powerful system where chat participation and management happen entirely through sharing and using capability URLs, making it easy to understand and use while maintaining fine-grained control over permissions.

notes/Node.Town/Xmas 2024 Disability Adventure.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,14 @@ When it comes to implementing the actor mesh cluster, there are some interesting
1616

1717
The actor management system raises some interesting questions too. Currently, when we start the server, it launches several actors with specific capabilities. We need to carefully consider whether to duplicate these across all instances or have certain specialized nodes. While redundancy is important - if one server goes down, you want others to pick up the slack - there might be cases where you want certain services to run on specific nodes only.
1818

19-
For the clustering implementation, I'm considering using a [[Message Bus]] approach, possibly MQTT or NATS. Since we're working within a trusted cluster on an internal network, we can be a bit more relaxed about security (no need for certificates and TLS). One machine could run as a hub, or perhaps one per region, which might be simpler than managing peer-to-peer connections. This needs to tie into our handling of names, permanence, and [[Actor Identity]], possibly through a multiplexed actor system.
19+
For the clustering implementation, I'm considering using a [[Message Bus]] approach, possibly MQTT or NATS. Since we're working within a trusted cluster on an internal network, we can be a bit more relaxed about security (no need for certificates and TLS). One machine could run as a hub, or perhaps one per region, which might be simpler than managing peer-to-peer connections. This needs to tie into our handling of names, permanence, and [[Actor Identity]], possibly through a multiplexed actor system.
20+
21+
## ugh field report
22+
23+
I wonder what it is about the system that makes me so reluctant to do real time stuff you know with Web sockets and live updating HTML stuff it just feels like somehow horrible and I'm not exactly sure why.
24+
25+
Some of it is just injury that it's hard to do anything so I'm reluctant to get into the weeds maybe I should have a little nap.
26+
27+
## simple chat session
28+
29+
So I started implementing some kind of simple chat session avoidance thingy and I'm wondering how to do it like OK I click chat and I get a thing with a text box but now I want to invite somebody like I wanna invite my brother to chat with me that means I want to be able to maybe like type his name into a form and then click create participant or invite participant or maybe there should not be ascend prompt and the chat itself that should be something you get when you join and you join as a user name you join as

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ dependencies = [
9393
# YouTube video downloader
9494
"yt-dlp>=2023.12.30",
9595
"nats-py>=2.9.0",
96+
"watchfiles>=0.21.0",
9697
]
9798

9899
[tool.uv.sources]

src/bubble/cli/serve.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from bubble.http.cert import generate_self_signed_cert
2525
from bubble.http.town import Site
2626
from bubble.mesh.base import this, spawn
27-
from bubble.http.tools import SheetEditor
27+
from bubble.http.tools import ChatCreator, SheetEditor
2828

2929
logger = structlog.get_logger()
3030
CONFIG = Namespace("https://bubble.node.town/vocab/config#")
@@ -83,14 +83,16 @@ def load_config(
8383

8484
@app.command()
8585
def serve(
86-
bind: str = Option(None, "--bind", help="Bind address"),
87-
base_url: str = BaseUrl,
86+
bind: Optional[str] = Option(None, "--bind", help="Bind address"),
87+
base_url: Optional[str] = BaseUrl,
8888
repo_path: str = RepoPath,
8989
shell: bool = Option(False, "--shell", help="Start a bash subshell"),
90-
cert_file: str = Option(
90+
cert_file: Optional[str] = Option(
9191
None, "--cert", help="SSL certificate file path"
9292
),
93-
key_file: str = Option(None, "--key", help="SSL private key file path"),
93+
key_file: Optional[str] = Option(
94+
None, "--key", help="SSL private key file path"
95+
),
9496
self_signed: bool = Option(
9597
False,
9698
"--self-signed",
@@ -189,10 +191,6 @@ def get_bash_prompt():
189191
elif cert_file and key_file:
190192
config.certfile = cert_file
191193
config.keyfile = key_file
192-
else:
193-
logger.info(
194-
"Running in HTTP mode - ensure HTTPS termination is handled by your reverse proxy"
195-
)
196194

197195
logger.info(
198196
"starting Node.Town",
@@ -202,6 +200,7 @@ def get_bash_prompt():
202200
)
203201

204202
town = Site(base_url, bind, repo)
203+
205204
if nats_url:
206205
logger.info("Setting up NATS clustering", nats_url=nats_url)
207206
await town.setup_nats(nats_url)
@@ -225,14 +224,21 @@ def get_bash_prompt():
225224
},
226225
)
227226

228-
editor = await spawn(
229-
nursery,
230-
SheetEditor(),
231-
name="sheet editor",
227+
town.vat.link_actor_to_identity(
228+
await spawn(
229+
nursery,
230+
SheetEditor(),
231+
name="sheet editor",
232+
)
232233
)
233234

234-
# Link supervisor to the town's identity
235-
town.vat.link_actor_to_identity(editor)
235+
town.vat.link_actor_to_identity(
236+
await spawn(
237+
nursery,
238+
ChatCreator(),
239+
name="chat creator",
240+
)
241+
)
236242

237243
nursery.start_soon(
238244
run_server, town.get_fastapi_app(), config

src/bubble/http/page.py

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import json
1414

1515
from contextlib import contextmanager
16+
from random import choice
1617
from urllib.parse import quote
1718

1819

@@ -48,43 +49,34 @@ def json_assignment_script(variable_name: str, value: dict):
4849

4950
@contextmanager
5051
def base_html(title: str):
51-
"""Create the base HTML structure for our pages.
52-
53-
Like a theater stage, we set up the basic structure where our
54-
content will perform. The head section is our backstage area,
55-
where we prepare all the scripts and styles that make the show
56-
possible.
57-
"""
58-
with tag("html"):
59-
with tag("head"):
60-
with tag("title"):
52+
with tag.html(lang="en"):
53+
with tag.head():
54+
with tag.title():
6155
text(title)
62-
# Our costume department - where we dress up our content
63-
tag("link", rel="stylesheet", href="/static/css/output.css")
64-
tag("link", rel="stylesheet", href="/static/audio-player.css")
65-
# The stage machinery - scripts that make things move
56+
57+
tag.link(rel="stylesheet", href="/static/css/output.css")
58+
tag.link(rel="stylesheet", href="/static/audio-player.css")
59+
6660
for script in cdn_scripts:
67-
tag("script", src=script)
68-
tag("script", type="module", src="/static/type-writer.js")
69-
tag("script", type="module", src="/static/voice-writer.js")
70-
tag("script", type="module", src="/static/audio-recorder.js")
71-
tag("script", type="module", src="/static/live.js")
61+
tag.script(src=script)
62+
63+
tag.script(type="module", src="/static/type-writer.js")
64+
tag.script(type="module", src="/static/voice-writer.js")
65+
tag.script(type="module", src="/static/audio-recorder.js")
66+
tag.script(type="module", src="/static/live.js")
7267
json_assignment_script("htmx.config", htmx_config)
7368

74-
# The stage itself - where the content performs
75-
with tag(
76-
"body",
69+
with tag.body(
7770
classes="bg-white dark:bg-slate-950 text-gray-900 dark:text-stone-50",
7871
):
7972
yield
80-
# The encore - scripts that run after the main content
81-
tag("script", type="module", src="/static/audio-player.js")
82-
tag(
83-
"script",
73+
74+
tag.script(type="module", src="/static/audio-player.js")
75+
tag.script(
8476
type="module",
8577
src="/static/voice-recorder-writer.js",
8678
)
87-
tag("script", type="module", src="/static/jsonld-socket.js")
79+
tag.script(type="module", src="/static/jsonld-socket.js")
8880

8981

9082
def urlquote(id: str):
@@ -132,8 +124,46 @@ def render_entry(label: str, value: str, href: str | URIRef):
132124

133125
@html.div(classes=status_bar_style)
134126
def render_status_bar():
127+
advice_texts = [
128+
"Do not take notes on criminal conspiracies.",
129+
"Every system is broken but you can still try.",
130+
"Your file naming conventions are very interesting.",
131+
"Write it down—if it's bad, delete it later.",
132+
"The cloud is just someone else's computer.",
133+
"You have permission to start poorly. ❤️",
134+
"All good ideas look bad in the beginning. 😭",
135+
"This app has no opinions about your life choices.",
136+
"Make a backup right now. Trust me.",
137+
"Complexity is very, very seductive. Are you up for it, playboy?",
138+
"You are not legally obligated to finish everything you start.",
139+
"This app will remember for you. Do not stress today.",
140+
"Most thoughts do not deserve a post-it note.",
141+
"The best version of your idea is the one that exists right now.",
142+
"No one is grading your to-do list.",
143+
"Just because it's a draft doesn't mean it's bad in ANY way.",
144+
"Not every project needs a name. Call this one Freckles.",
145+
"Begin. The rest is easier.",
146+
"If you use this app to explain your feelings, use also a napkin.",
147+
"Your 17th idea today will likely be the good one!.",
148+
"Being busy is a thing. That happens. I think.",
149+
"This app is your therapist. Just kidding. Double jinx.",
150+
"Genius is forgetting bad ideas quickly.",
151+
"Despite the widespread enthusiasm, we have not yet implemented writer's block.",
152+
"Congratulations. You're overthinking it.",
153+
"Perfectionism is not always just a fancy word for procrastination.",
154+
"Spend more time naming than working.",
155+
"Write as though you realize that you will not even remember this tomorrow.",
156+
"Progress doesn't happen according to a plan. Unless you have a pretty good plan.",
157+
"Your creative process is what I call my existential crisis.",
158+
"This app contains a roadmap for your life.",
159+
"There are no rules. Especially syntax ones.",
160+
]
161+
135162
with tag.div(classes="flex items-center"):
136-
pass # todo: add some content here
163+
with tag.span(
164+
classes="text-sm text-gray-500 dark:text-gray-400 pl-4"
165+
):
166+
text(choice(advice_texts))
137167

138168
with tag.div(classes="flex items-center gap-6"):
139169
vat = current_vat.get()

0 commit comments

Comments
 (0)