This commit is contained in:
Sam
2026-06-10 11:51:56 -05:00
commit 66ba338b81
57 changed files with 5509 additions and 0 deletions

319
priv/experiments/forms.pug Normal file
View File

@@ -0,0 +1,319 @@
forms-
// global
member-(id="1" email="samrussell99@pm.me" first-name="Sam" last-name="Russell" password="$argon2id$v=19$m=65536,t=3,p=4$n/8BaBisEnBaQNbkxzs1VA$dvvnupWNtB5w5qTBgEciDsNA6rOgXaEypcEK1A0ndLM" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="CEO" bio="This is my bio" notes="no notes" created="2026-01-15 09:58:01.0072" updated-at="2026-01-15 09:58:01.0072")
member-(id="2" email="freddyjkrueger@gmail.com" first-name="Freddy" last-name="Krueger" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="Villain" bio="This is my bio" notes="no notes" created="2026-01-13 13:38:46.0810" updated-at="2026-01-13 13:38:46.0810")
member-(id="3" email="harmysmarmy@gmail.com" first-name="Harmy" last-name="Smarmy" password="$argon2id$v=19$m=65536,t=3,p=4$FAhGtCtqNAQ19tBYD73wXQ$0AM/khyBFFuX2mv0ieqtGfsXRgtEldWKFwyeV3BA3Xk" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="Associate" bio="This is my bio" notes="no notes" created="2026-01-13 13:41:41.0722" updated-at="2026-01-13 13:41:41.0722")
member-(id="4" email="matiascarulli@gmail.com" first-name="Matias" last-name="Carulli" password="$argon2id$v=19$m=65536,t=3,p=4$U2FsdGVkc3M$08SY0x7DUPwTV12ZckHdNg" address1="1234 address NW 12th St" city="Miramar" state="Florida" zipcode="33029" country="US" county="Broward" phone="123-456-789" title="Developer" bio="This is my bio" image-path="/db/images/users/member-4/profile.png" notes="no notes" created="2026-03-15 13:41:41.0722" updated-at="2026-03-15 13:41:41.0722")
member-(id="5" email="boulder@example.com" first-name="CU" last-name="Boulder" password="$argon2id$v=19$m=65536,t=3,p=4$CQwOYXNwwsLBP1s/zcZNJg$OM/wwVP5U+QUnAEDKAjk5mpvujpOzpT0XkouDcmHT8E" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="Associate" bio="This is my bio" notes="no notes" created="2026-03-26 01:03:18.803016+00" updated-at="2026-03-26 01:03:18.803016+00")
member-(id="6" email="sarah.mcintyre@example.com" first-name="Sarah" last-name="McIntyre" password="$argon2id$v=19$m=65536,t=3,p=4$U2FsdGVkc3M$08SY0x7DUPwTV12ZckHdNg" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="Designer" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00")
member-(id="7" email="marcus.webb@example.com" first-name="Marcus" last-name="Webb" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="Engineer" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00")
member-(id="8" email="priya.anand@example.com" first-name="Priya" last-name="Anand" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="PM" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00")
member-(id="9" email="jordan.kim@example.com" first-name="Jordan" last-name="Kim" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="Designer" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00")
app-(id="1" name="settings")
app-(id="2" name="people")
app-(id="3" name="calendar")
app-(id="4" name="treasury")
app-(id="5" name="politics")
app-(id="6" name="files")
app-(id="7" name="jobs")
app-(id="8" name="tasks")
app-(id="9" name="chat")
app-(id="10" name="announcements")
permission-(id="1" key="events.add" app-id="3" description="Can add events")
permission-(id="2" key="events.delete" app-id="3" description="Can delete events")
permission-(id="3" key="events.edit" app-id="3" description="Can edit events")
permission-(id="4" key="events.get" app-id="3" description="Can view events")
permission-(id="5" key="jobs.add" app-id="7" description="Can add jobs")
permission-(id="6" key="jobs.delete" app-id="7" description="Can delete jobs")
permission-(id="7" key="jobs.edit" app-id="7" description="Can edit jobs")
permission-(id="8" key="jobs.get" app-id="7" description="Can view jobs")
permission-(id="9" key="announcements.add" app-id="10" description="Can add announcements")
permission-(id="10" key="announcements.delete" app-id="10" description="Can delete announcements")
permission-(id="11" key="announcements.edit" app-id="10" description="Can edit announcements")
permission-(id="12" key="announcements.get" app-id="10" description="Can view announcements")
permission-(id="13" key="role_apps.edit" app-id="1" description="Can edit role apps")
permission-(id="14" key="roles.create" app-id="1" description="Can create roles")
permission-(id="15" key="roles.delete" app-id="1" description="Can delete roles")
permission-(id="16" key="role_notifications.edit" app-id="1" description="Can edit role notifications")
permission-(id="17" key="chats.create" app-id="9" description="Can create chats")
permission-(id="18" key="chats.edit" app-id="9" description="Can edit chats")
permission-(id="19" key="chats.delete" app-id="9" description="Can delete chats")
permission-(id="20" key="chats_message.send" app-id="9" description="Can send messages")
permission-(id="21" key="chats_message.edit" app-id="9" description="Can edit messages")
permission-(id="22" key="chats_message.delete" app-id="9" description="Can delete messages")
member-app-(id="1" member-id="1" app-id="1")
member-app-(id="2" member-id="4" app-id="1")
member-app-(id="3" member-id="6" app-id="1")
member-app-(id="4" member-id="1" app-id="2")
member-app-(id="5" member-id="4" app-id="2")
member-app-(id="6" member-id="6" app-id="2")
member-app-(id="7" member-id="1" app-id="3")
member-app-(id="8" member-id="4" app-id="3")
member-app-(id="9" member-id="6" app-id="3")
member-app-(id="10" member-id="1" app-id="4")
member-app-(id="11" member-id="4" app-id="4")
member-app-(id="12" member-id="6" app-id="4")
member-app-(id="13" member-id="1" app-id="5")
member-app-(id="14" member-id="4" app-id="5")
member-app-(id="15" member-id="6" app-id="5")
member-app-(id="16" member-id="1" app-id="6")
member-app-(id="17" member-id="4" app-id="6")
member-app-(id="18" member-id="6" app-id="6")
member-app-(id="19" member-id="1" app-id="7")
member-app-(id="20" member-id="4" app-id="7")
member-app-(id="21" member-id="6" app-id="7")
member-app-(id="22" member-id="1" app-id="8")
member-app-(id="23" member-id="4" app-id="8")
member-app-(id="24" member-id="6" app-id="8")
member-app-(id="25" member-id="1" app-id="9")
member-app-(id="26" member-id="4" app-id="10")
member-app-(id="27" member-id="6" app-id="9")
// network 1: Captured Sun
network-(id="1" name="Captured Sun" logo="cs.svg" abbreviation="cs" stripe-account-id="acct_1Sn6DwLpyskwAml9" created="2026-01-10 09:58:01.0074")
network-plan-(id="1" network-id="1" stripe-price-id="price_1T3uaxLpyskwAml9p0r0nh2h" name="Patron Membership" price="200.00" description="Members 40+" active="true" created="2026-03-29 22:14:45.414163")
network-plan-(id="2" network-id="1" stripe-price-id="price_1T3uaQLpyskwAml9rZAKBcy0" name="Regular Membership" price="100.00" description="Members 18-40" active="true" created="2026-03-29 22:14:45.414163")
join-code-(id="1" code="cs" network-id="1")
member-network-(id="1" member-id="1" network-id="1" created="2025-11-24 00:54:36.0784")
member-network-(id="2" member-id="2" network-id="1" created="2026-01-13 13:14:28.0178")
member-network-(id="3" member-id="3" network-id="1" created="2026-01-13 13:28:35.0701")
role-(id="1" network-id="1" name="admin" is-default="false")
role-(id="2" network-id="1" name="member" is-default="true")
role-app-(id="1" role-id="1" app-id="1" network-id="1")
role-app-(id="2" role-id="1" app-id="2" network-id="1")
role-app-(id="3" role-id="1" app-id="3" network-id="1")
role-app-(id="4" role-id="1" app-id="4" network-id="1")
role-app-(id="5" role-id="1" app-id="5" network-id="1")
role-app-(id="6" role-id="1" app-id="6" network-id="1")
role-app-(id="7" role-id="1" app-id="7" network-id="1")
role-app-(id="8" role-id="1" app-id="8" network-id="1")
role-app-(id="9" role-id="1" app-id="9" network-id="1")
role-app-(id="10" role-id="1" app-id="10" network-id="1")
role-permission-(id="1" role-id="1" permission-key="events.get" network-id="1")
role-permission-(id="2" role-id="1" permission-key="jobs.get" network-id="1")
role-permission-(id="3" role-id="1" permission-key="announcements.get" network-id="1")
role-permission-(id="4" role-id="1" permission-key="events.add" network-id="1")
role-permission-(id="5" role-id="1" permission-key="events.delete" network-id="1")
role-permission-(id="6" role-id="1" permission-key="events.edit" network-id="1")
role-permission-(id="7" role-id="1" permission-key="jobs.add" network-id="1")
role-permission-(id="8" role-id="1" permission-key="jobs.delete" network-id="1")
role-permission-(id="9" role-id="1" permission-key="jobs.edit" network-id="1")
role-permission-(id="10" role-id="1" permission-key="announcements.add" network-id="1")
role-permission-(id="11" role-id="1" permission-key="announcements.delete" network-id="1")
role-permission-(id="12" role-id="1" permission-key="announcements.edit" network-id="1")
role-permission-(id="13" role-id="1" permission-key="role_apps.edit" network-id="1")
role-permission-(id="14" role-id="1" permission-key="roles.create" network-id="1")
role-permission-(id="15" role-id="1" permission-key="roles.delete" network-id="1")
role-permission-(id="16" role-id="1" permission-key="chats.create" network-id="1")
role-permission-(id="17" role-id="1" permission-key="chats.edit" network-id="1")
role-permission-(id="18" role-id="1" permission-key="chats.delete" network-id="1")
role-permission-(id="19" role-id="1" permission-key="chats_message.send" network-id="1")
role-permission-(id="20" role-id="1" permission-key="chats_message.edit" network-id="1")
role-permission-(id="21" role-id="1" permission-key="chats_message.delete" network-id="1")
role-permission-(id="22" role-id="1" permission-key="role_notifications.edit" network-id="1")
role-permission-(id="23" role-id="2" permission-key="events.get" network-id="1")
role-permission-(id="24" role-id="2" permission-key="jobs.get" network-id="1")
role-permission-(id="25" role-id="2" permission-key="announcements.get" network-id="1")
role-permission-(id="26" role-id="2" permission-key="events.add" network-id="1")
role-permission-(id="27" role-id="2" permission-key="chats.create" network-id="1")
role-permission-(id="28" role-id="2" permission-key="chats_message.send" network-id="1")
member-role-(id="1" member-id="1" role-id="1" granted-by="1" network-id="1")
member-role-(id="2" member-id="2" role-id="2" granted-by="1" network-id="1")
member-role-(id="3" member-id="3" role-id="2" granted-by="1" network-id="1")
join-form-(schema="org_1" id="1" fname="James" lname="Mitchell" email="james.mitchell@gmail.com" phone="512-555-0101" county="Comal" time="2025-12-16 23:11:31.0011" network-id="1")
join-form-(schema="org_1" id="2" fname="Rachel" lname="Torres" email="rachel.torres@yahoo.com" phone="512-555-0102" county="Bexar" time="2025-12-19 19:23:12.0717" network-id="1")
join-form-(schema="org_1" id="3" fname="David" lname="Nguyen" email="david.nguyen@gmail.com" phone="830-555-0103" county="Comal" time="2026-01-06 16:55:29.0288" network-id="1")
join-form-(schema="org_1" id="4" fname="Emily" lname="Sanders" email="emily.sanders@outlook.com" phone="210-555-0104" county="Hays" time="2026-01-07 17:14:01.0711" network-id="1")
contact-form-(schema="org_1" id="1" fname="Marcus" lname="Webb" email="marcus.webb@gmail.com" phone="512-555-0201" county="Comal" message="Interested in volunteering at upcoming events." time="2025-12-29 13:20:28.0157" network-id="1")
contact-form-(schema="org_1" id="2" fname="Sandra" lname="Holloway" email="sandra.holloway@gmail.com" phone="830-555-0202" county="Comal" message="Would love to connect with your organization." time="2025-12-30 22:10:24.0971" network-id="1")
contact-form-(schema="org_1" id="3" fname="Robert" lname="Finley" email="robert.finley@gmail.com" phone="210-555-0203" county="Comal" message="Looking forward to getting more involved locally." time="2026-01-10 21:23:51.0073" network-id="1")
contact-form-(schema="org_1" id="4" fname="Barbara" lname="Crane" email="barbara.crane@outlook.com" phone="512-555-0204" county="Comal" message="Please reach out regarding the next meeting schedule." time="2026-01-10 21:23:54.0841" network-id="1")
// network 2: Hyperia
network-(id="2" name="Hyperia" logo="hyperia.svg" abbreviation="hyperia" stripe-account-id="acct_1S4w0GHZemeF9CKR" created="2026-01-10 09:58:01.0074")
join-code-(id="2" code="hyperia" network-id="2")
member-network-(id="4" member-id="1" network-id="2" created="2026-01-13 13:28:35.0701")
member-network-(id="5" member-id="4" network-id="2" created="2026-03-15 13:28:35.0701")
member-network-(id="6" member-id="6" network-id="2" created="2026-02-01 09:00:00+00")
member-network-(id="7" member-id="7" network-id="2" created="2026-02-01 09:00:00+00")
member-network-(id="8" member-id="8" network-id="2" created="2026-02-01 09:00:00+00")
member-network-(id="9" member-id="9" network-id="2" created="2026-02-01 09:00:00+00")
role-(id="3" network-id="2" name="admin" is-default="false")
role-(id="4" network-id="2" name="member" is-default="true")
role-app-(id="11" role-id="3" app-id="1" network-id="2")
role-app-(id="12" role-id="3" app-id="2" network-id="2")
role-app-(id="13" role-id="3" app-id="3" network-id="2")
role-app-(id="14" role-id="3" app-id="4" network-id="2")
role-app-(id="15" role-id="3" app-id="5" network-id="2")
role-app-(id="16" role-id="3" app-id="6" network-id="2")
role-app-(id="17" role-id="3" app-id="7" network-id="2")
role-app-(id="18" role-id="3" app-id="8" network-id="2")
role-app-(id="19" role-id="3" app-id="9" network-id="2")
role-app-(id="20" role-id="3" app-id="10" network-id="2")
role-permission-(id="29" role-id="3" permission-key="events.get" network-id="2")
role-permission-(id="30" role-id="3" permission-key="jobs.get" network-id="2")
role-permission-(id="31" role-id="3" permission-key="announcements.get" network-id="2")
role-permission-(id="32" role-id="3" permission-key="events.add" network-id="2")
role-permission-(id="33" role-id="3" permission-key="events.delete" network-id="2")
role-permission-(id="34" role-id="3" permission-key="events.edit" network-id="2")
role-permission-(id="35" role-id="3" permission-key="jobs.add" network-id="2")
role-permission-(id="36" role-id="3" permission-key="jobs.delete" network-id="2")
role-permission-(id="37" role-id="3" permission-key="jobs.edit" network-id="2")
role-permission-(id="38" role-id="3" permission-key="announcements.add" network-id="2")
role-permission-(id="39" role-id="3" permission-key="announcements.delete" network-id="2")
role-permission-(id="40" role-id="3" permission-key="announcements.edit" network-id="2")
role-permission-(id="41" role-id="3" permission-key="role_apps.edit" network-id="2")
role-permission-(id="42" role-id="3" permission-key="roles.create" network-id="2")
role-permission-(id="43" role-id="3" permission-key="roles.delete" network-id="2")
role-permission-(id="44" role-id="3" permission-key="chats.create" network-id="2")
role-permission-(id="45" role-id="3" permission-key="chats.edit" network-id="2")
role-permission-(id="46" role-id="3" permission-key="chats.delete" network-id="2")
role-permission-(id="47" role-id="3" permission-key="chats_message.send" network-id="2")
role-permission-(id="48" role-id="3" permission-key="chats_message.edit" network-id="2")
role-permission-(id="49" role-id="3" permission-key="chats_message.delete" network-id="2")
role-permission-(id="50" role-id="3" permission-key="role_notifications.edit" network-id="2")
role-permission-(id="51" role-id="4" permission-key="events.get" network-id="2")
role-permission-(id="52" role-id="4" permission-key="jobs.get" network-id="2")
role-permission-(id="53" role-id="4" permission-key="announcements.get" network-id="2")
role-permission-(id="54" role-id="4" permission-key="events.add" network-id="2")
role-permission-(id="55" role-id="4" permission-key="chats.create" network-id="2")
role-permission-(id="56" role-id="4" permission-key="chats_message.send" network-id="2")
member-role-(id="4" member-id="1" role-id="3" granted-by="1" network-id="2")
member-role-(id="5" member-id="4" role-id="3" granted-by="1" network-id="2")
member-role-(id="6" member-id="6" role-id="3" granted-by="1" network-id="2")
member-role-(id="7" member-id="6" role-id="4" granted-by="1" network-id="2")
member-role-(id="8" member-id="7" role-id="4" granted-by="1" network-id="2")
member-role-(id="9" member-id="8" role-id="4" granted-by="1" network-id="2")
member-role-(id="10" member-id="9" role-id="4" granted-by="1" network-id="2")
calendar-(schema="events" id="1" network-id="2" owner-id="1" name="Main Calendar" description="The main calendar for the network" color="#9E1C29")
calendar-(schema="events" id="2" network-id="2" owner-id="1" name="Sub-Calendar" description="Sub-calendar for the network" color="#3D6FAD")
calendar-(schema="events" id="3" network-id="2" owner-id="1" name="Sub-Calendar 2" description="Another sub-calendar for the network" color="#2A8636")
event-recurrence-(schema="events" id="1" frequency="weekly" interval="1" days-of-week="{2}" network-id="2")
event-recurrence-(schema="events" id="2" frequency="weekly" interval="2" days-of-week="{3}" count="10" network-id="2")
event-recurrence-(schema="events" id="3" frequency="weekly" interval="1" days-of-week="{1,3,5}" network-id="2")
event-(schema="events" id="1" network-id="2" creator-id="1" title="Client meeting" description="Meeting with big client for app deployment" time-start="2026-04-01T17:30:00.000Z" time-end="2026-04-01T19:00:00.000Z" location="Virtual" all-day="false" recurrence-id="2")
event-(schema="events" id="2" network-id="2" creator-id="1" title="Networking Event" description="Networking event for young professionals" time-start="2026-04-04T04:00:00.000Z" time-end="2026-04-06T04:00:00.000Z" location="GB Center" all-day="true")
event-(schema="events" id="3" network-id="2" creator-id="1" title="App deployment party" description="Come celebrate the app deployment!" time-start="2026-04-05T16:00:00.000Z" time-end="2026-04-05T21:30:00.000Z" location="Captured Sun HQ" all-day="false")
event-(schema="events" id="4" network-id="2" creator-id="4" title="Work on frontend changes" description="Reminder for work #1" time-start="2026-04-02T15:00:00.000Z" time-end="2026-04-02T19:00:00.000Z" all-day="false")
event-(schema="events" id="5" network-id="2" creator-id="4" title="Day off" description="I dont have to work today" time-start="2026-04-03T04:00:00.000Z" time-end="2026-04-03T04:00:00.000Z" all-day="true")
event-(schema="events" id="6" network-id="2" creator-id="4" title="Scrum Meeting - Main Team" description="Agile Week 32" time-start="2026-03-31T03:00:00.000Z" time-end="2026-03-31T06:15:00.000Z" location="Virtual" all-day="false" recurrence-id="1")
event-(schema="events" id="7" network-id="2" creator-id="1" title="Meeting with John Smiith" description="Lorem ipsum elorum" time-start="2026-03-31T18:30:00.000Z" time-end="2026-03-31T19:30:00.000Z" location="Virtual" all-day="false")
event-(schema="events" id="8" network-id="2" creator-id="1" title="Meeting with Jane Doe" description="Lorem ipsum elorum" time-start="2026-03-31T19:30:00.000Z" time-end="2026-03-31T19:45:00.000Z" location="Virtual" all-day="false")
event-(schema="events" id="9" network-id="2" creator-id="1" title="Meeting with president" description="Lorem ipsum elorum" time-start="2026-03-31T19:45:00.000Z" time-end="2026-03-31T21:15:00.000Z" location="Virtual" all-day="false")
event-(schema="events" id="10" network-id="2" creator-id="1" title="Meeting with investors" description="Lorem ipsum elorum" time-start="2026-03-31T21:30:00.000Z" time-end="2026-03-31T23:00:00.000Z" location="Virtual" all-day="false")
event-(schema="events" id="11" network-id="2" creator-id="1" title="Review Github changes" description="Review pushes from members to ensure all is well" time-start="2026-04-03T04:00:00.000Z" time-end="2026-04-03T04:00:00.000Z" all-day="true" recurrence-id="3")
event-calendar-(schema="events" id="1" event-id="1" calendar-id="1" network-id="2")
event-calendar-(schema="events" id="2" event-id="2" calendar-id="2" network-id="2")
event-calendar-(schema="events" id="3" event-id="3" calendar-id="1" network-id="2")
event-calendar-(schema="events" id="4" event-id="3" calendar-id="3" network-id="2")
event-calendar-(schema="events" id="5" event-id="4" calendar-id="2" network-id="2")
event-calendar-(schema="events" id="6" event-id="5" calendar-id="2" network-id="2")
event-calendar-(schema="events" id="7" event-id="6" calendar-id="1" network-id="2")
event-calendar-(schema="events" id="8" event-id="6" calendar-id="2" network-id="2")
event-calendar-(schema="events" id="9" event-id="7" calendar-id="3" network-id="2")
event-calendar-(schema="events" id="10" event-id="8" calendar-id="2" network-id="2")
event-calendar-(schema="events" id="11" event-id="8" calendar-id="3" network-id="2")
event-calendar-(schema="events" id="12" event-id="9" calendar-id="3" network-id="2")
event-calendar-(schema="events" id="13" event-id="10" calendar-id="3" network-id="2")
event-calendar-(schema="events" id="14" event-id="11" calendar-id="3" network-id="2")
job-(id="1" network-id="2" creator-id="1" title="Senior Frontend Engineer" company="Acme Corp" location="San Francisco, CA" employment-type="full-time" experience-level="senior" department="Engineering" salary-number="165000" salary-period="year" applicants="47" skills="React,TypeScript,CSS,GraphQL,Node.js" description="We're looking for a Senior Frontend Engineer to join our growing product team. You'll work closely with designers, backend engineers, and product managers to build fast, accessible, and beautiful web experiences.

You'll have ownership over large features and be expected to make architectural decisions. We ship every week and care deeply about code quality and UX." created="2026-04-29 10:00:00" updated-at="2026-04-29 10:00:00")
job-(id="2" network-id="2" creator-id="1" title="Product Designer" company="Blue River" location="New York, NY" employment-type="full-time" experience-level="mid" department="Design" salary-number="120000" salary-period="year" applicants="112" skills="Figma,Design Systems,Prototyping,User Research" description="Blue River is hiring a Product Designer to lead design across our core consumer product. You'll partner with PMs and engineers to take features from zero to one, establish design patterns, and advocate for users in every decision.

We're a small, fast-moving team and designers have a huge impact here." created="2026-04-26 09:00:00" updated-at="2026-04-26 09:00:00")
job-(id="3" network-id="2" creator-id="1" title="Backend Engineer" company="Orbit Systems" location="Remote" employment-type="contract" experience-level="mid" department="Platform" salary-number="95" salary-period="hour" applicants="29" skills="Go,PostgreSQL,Kubernetes,gRPC,Redis" description="6-month contract (potential to extend) for a backend engineer to help scale our platform infrastructure. You'll be responsible for building and maintaining APIs used by millions of users, optimizing database performance, and improving system reliability." created="2026-04-30 14:00:00" updated-at="2026-04-30 14:00:00")
job-(id="4" network-id="2" creator-id="1" title="Marketing Manager" company="Groundwork" location="Austin, TX" employment-type="full-time" experience-level="mid" department="Marketing" salary-number="98000" salary-period="year" applicants="88" skills="SEO,Content Strategy,Analytics,Paid Acquisition,Email Marketing" description="We need a Marketing Manager to own our top-of-funnel growth. You'll manage content, paid channels, and email campaigns — and be the person who keeps the brand consistent across every touchpoint. You'll report directly to our Head of Growth." created="2026-04-21 11:00:00" updated-at="2026-04-21 11:00:00")
job-(id="5" network-id="2" creator-id="1" title="Data Analyst" company="Compass Data" location="Chicago, IL" employment-type="full-time" experience-level="entry" department="Analytics" salary-number="75000" salary-period="year" applicants="203" skills="SQL,Python,Tableau,dbt,Excel" description="A great entry-level opportunity for someone who loves data. You'll work with our analytics team to build dashboards, run ad-hoc analysis, and help business teams make data-driven decisions. We'll invest in your growth and give you plenty of mentorship." created="2026-04-28 08:30:00" updated-at="2026-04-28 08:30:00")
job-(id="6" network-id="2" creator-id="1" title="iOS Engineer" company="Fieldwork" location="Seattle, WA" employment-type="full-time" experience-level="senior" department="Mobile" salary-number="175000" salary-period="year" applicants="34" skills="Swift,SwiftUI,Combine,CoreData,Xcode" description="Fieldwork is building next-generation tools for field service teams. Our iOS app is the most important surface we have — technicians use it every day on job sites. We need a senior iOS engineer who cares about performance, offline reliability, and a great UX." created="2026-04-24 13:00:00" updated-at="2026-04-24 13:00:00")
job-(id="7" network-id="2" creator-id="1" title="Operations Coordinator" company="Maple & Co" location="Boston, MA" employment-type="part-time" experience-level="entry" department="Operations" salary-number="28" salary-period="hour" applicants="61" skills="Project Management,Excel,Communication,Scheduling" description="Part-time role (20 hrs/week) helping our operations team stay organized. You'll coordinate schedules, manage vendor relationships, and help improve internal workflows. Ideal for someone who's highly organized and excited to grow into a full-time ops role." created="2026-04-17 10:00:00" updated-at="2026-04-17 10:00:00")
job-(id="8" network-id="2" creator-id="1" title="Machine Learning Intern" company="NeuralPath" location="Remote" employment-type="internship" experience-level="entry" department="AI Research" salary-number="8000" salary-period="month" applicants="394" skills="Python,PyTorch,Linear Algebra,Git" description="Summer internship (12 weeks) on our ML research team. You'll work alongside researchers on real problems in NLP and recommendation systems. We expect you to ship something you're proud of by the end of the summer. Strong preference for candidates who can start June 2." created="2026-05-01 09:00:00" updated-at="2026-05-01 09:00:00")
announcement-(id="1" network-id="2" creator-id="1" text="This is the first announcement" created="2026-01-13 15:37:00.0000" updated-at="2026-01-13 15:37:00.0000")
announcement-(id="2" network-id="2" creator-id="1" text="Here goes another announcement." created="2026-01-13 15:39:00.0000" updated-at="2026-01-13 15:39:00.0000")
announcement-(id="3" network-id="2" creator-id="4" text="My first announcement!" created="2026-01-14 15:41:00.0000" updated-at="2026-01-14 15:41:00.0000")
announcement-(id="4" network-id="2" creator-id="1" text="Testing announcement." created="2026-02-17 12:30:00.0000" updated-at="2026-02-17 12:30:00.0000")
announcement-(id="5" network-id="2" creator-id="4" text="Quick fire!" created="2026-03-23 15:56:00.0000" updated-at="2026-03-23 15:56:00.0000")
announcement-(id="6" network-id="2" creator-id="4" text="Quick fire!" created="2026-03-23 15:56:30.0000" updated-at="2026-03-23 15:56:30.0000")
announcement-(id="7" network-id="2" creator-id="4" text="Quick fire!" created="2026-03-23 15:57:00.0000" updated-at="2026-03-23 15:57:00.0000")
announcement-(id="8" network-id="2" creator-id="1" text="Trying another user." created="2026-02-05 15:56:30.0000" updated-at="2026-02-05 15:56:30.0000")
announcement-(id="9" network-id="2" creator-id="1" text="One last announcement." created="2026-04-01 15:57:00.0000" updated-at="2026-01-13 15:57:00.0000")
chat-(schema="chats" id="1" network-id="2" creator-id="4" type="dm" created="2026-04-01 09:00:00+00" updated-at="2026-05-01 11:42:00+00")
chat-(schema="chats" id="2" network-id="2" creator-id="4" type="dm" created="2026-04-01 09:00:00+00" updated-at="2026-05-01 10:30:00+00")
chat-(schema="chats" id="3" network-id="2" creator-id="4" type="dm" created="2026-04-01 09:00:00+00" updated-at="2026-04-30 18:00:00+00")
chat-(schema="chats" id="4" network-id="2" creator-id="4" type="group" name="Product Team" created="2026-04-01 09:00:00+00" updated-at="2026-05-01 11:51:00+00")
chat-(schema="chats" id="5" network-id="2" creator-id="4" type="group" name="Design Review" created="2026-04-01 09:00:00+00" updated-at="2026-04-30 10:00:00+00")
chat-(schema="chats" id="6" network-id="2" creator-id="4" type="channel" name="general" created="2026-04-01 09:00:00+00" updated-at="2026-05-01 11:55:00+00")
chat-(schema="chats" id="7" network-id="2" creator-id="4" type="channel" name="engineering" created="2026-04-01 09:00:00+00" updated-at="2026-05-01 09:00:00+00")
chat-(schema="chats" id="8" network-id="2" creator-id="8" type="announcement" name="Announcements" created="2026-04-01 09:00:00+00" updated-at="2026-05-01 00:00:00+00")
chat-member-(schema="chats" id="1" chat-id="1" member-id="4" last-read-at="2026-05-01 07:12:00+00" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="2" chat-id="1" member-id="6" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="3" chat-id="2" member-id="4" last-read-at="2026-05-01 10:30:00+00" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="4" chat-id="2" member-id="7" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="5" chat-id="3" member-id="4" last-read-at="2026-04-30 18:00:00+00" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="6" chat-id="3" member-id="8" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="7" chat-id="4" member-id="4" last-read-at="2026-05-01 04:30:00+00" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="8" chat-id="4" member-id="6" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="9" chat-id="4" member-id="7" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="10" chat-id="4" member-id="8" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="11" chat-id="5" member-id="4" last-read-at="2026-04-30 10:00:00+00" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="12" chat-id="5" member-id="6" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="13" chat-id="5" member-id="9" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="14" chat-id="6" member-id="1" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="15" chat-id="6" member-id="4" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="16" chat-id="6" member-id="6" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="17" chat-id="6" member-id="7" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="18" chat-id="6" member-id="8" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="19" chat-id="6" member-id="9" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="20" chat-id="7" member-id="1" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="21" chat-id="7" member-id="4" last-read-at="2026-05-01 09:00:00+00" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="22" chat-id="7" member-id="7" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="23" chat-id="8" member-id="1" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="24" chat-id="8" member-id="4" last-read-at="2026-04-29 12:00:00+00" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="25" chat-id="8" member-id="6" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="26" chat-id="8" member-id="7" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="27" chat-id="8" member-id="8" joined-at="2026-04-01 09:00:00+00" network-id="2")
chat-member-(schema="chats" id="28" chat-id="8" member-id="9" joined-at="2026-04-01 09:00:00+00" network-id="2")
message-(schema="chats" id="1" chat-id="1" sender-id="4" text="Hey Sarah, did you see the new design mockups?" sent-at="2026-04-30 12:00:00+00" updated-at="2026-04-30 12:00:00+00" network-id="2")
message-(schema="chats" id="2" chat-id="1" sender-id="6" text="Just looked — they're really clean. I love the new sidebar." sent-at="2026-04-30 12:30:00+00" updated-at="2026-04-30 12:30:00+00" network-id="2")
message-(schema="chats" id="3" chat-id="1" sender-id="4" text="Agreed. Alex did a great job." sent-at="2026-04-30 12:36:00+00" updated-at="2026-04-30 12:36:00+00" network-id="2")
message-(schema="chats" id="4" chat-id="1" sender-id="6" text="Are we going to ship this week or wait for the backend?" sent-at="2026-05-01 07:00:00+00" updated-at="2026-05-01 07:00:00+00" network-id="2")
message-(schema="chats" id="5" chat-id="1" sender-id="4" text="Let's aim for Thursday. I'll sync with Marcus." sent-at="2026-05-01 07:12:00+00" updated-at="2026-05-01 07:12:00+00" network-id="2")
message-(schema="chats" id="6" chat-id="1" sender-id="6" text="Sounds good 👍" sent-at="2026-05-01 07:18:00+00" updated-at="2026-05-01 07:18:00+00" network-id="2")
message-(schema="chats" id="7" chat-id="1" sender-id="6" text="Can you review the PR when you get a chance?" sent-at="2026-05-01 11:42:00+00" updated-at="2026-05-01 11:42:00+00" network-id="2")
message-(schema="chats" id="8" chat-id="2" sender-id="7" text="Hey, the API endpoint is returning 500s on staging." sent-at="2026-05-01 09:00:00+00" updated-at="2026-05-01 09:00:00+00" network-id="2")
message-(schema="chats" id="9" chat-id="2" sender-id="4" text="Oh no — is it the auth middleware again?" sent-at="2026-05-01 09:06:00+00" updated-at="2026-05-01 09:06:00+00" network-id="2")
message-(schema="chats" id="10" chat-id="2" sender-id="7" text="Yep. Same issue as last week." sent-at="2026-05-01 09:12:00+00" updated-at="2026-05-01 09:12:00+00" network-id="2")
message-(schema="chats" id="11" chat-id="2" sender-id="4" text="I'll patch it now. Give me 20 mins." sent-at="2026-05-01 09:15:00+00" updated-at="2026-05-01 09:15:00+00" network-id="2")
message-(schema="chats" id="12" chat-id="2" sender-id="7" text="Thanks, no rush." sent-at="2026-05-01 09:18:00+00" updated-at="2026-05-01 09:18:00+00" network-id="2")
message-(schema="chats" id="13" chat-id="2" sender-id="4" text="Fixed. Can you redeploy and check?" sent-at="2026-05-01 09:48:00+00" updated-at="2026-05-01 09:48:00+00" network-id="2")
message-(schema="chats" id="14" chat-id="2" sender-id="7" text="All green 🎉 Thanks!" sent-at="2026-05-01 09:54:00+00" updated-at="2026-05-01 09:54:00+00" network-id="2")
message-(schema="chats" id="15" chat-id="2" sender-id="4" text="I'll send over the specs by EOD" sent-at="2026-05-01 10:30:00+00" updated-at="2026-05-01 10:30:00+00" network-id="2")
message-(schema="chats" id="16" chat-id="3" sender-id="8" text="Quick question — what's the launch date for v2?" sent-at="2026-04-30 16:00:00+00" updated-at="2026-04-30 16:00:00+00" network-id="2")
message-(schema="chats" id="17" chat-id="3" sender-id="4" text="Still TBD, but we're targeting end of May." sent-at="2026-04-30 16:12:00+00" updated-at="2026-04-30 16:12:00+00" network-id="2")
message-(schema="chats" id="18" chat-id="3" sender-id="8" text="Got it. I'll update the roadmap doc." sent-at="2026-04-30 16:30:00+00" updated-at="2026-04-30 16:30:00+00" network-id="2")
message-(schema="chats" id="19" chat-id="3" sender-id="4" text="Perfect, thanks Priya." sent-at="2026-04-30 16:36:00+00" updated-at="2026-04-30 16:36:00+00" network-id="2")
message-(schema="chats" id="20" chat-id="3" sender-id="8" text="See you at the standup!" sent-at="2026-04-30 18:00:00+00" updated-at="2026-04-30 18:00:00+00" network-id="2")
message-(schema="chats" id="21" chat-id="4" sender-id="7" text="Morning everyone! API docs are updated." sent-at="2026-05-01 04:00:00+00" updated-at="2026-05-01 04:00:00+00" network-id="2")
message-(schema="chats" id="22" chat-id="4" sender-id="8" text="Nice work Marcus 🙌" sent-at="2026-05-01 04:06:00+00" updated-at="2026-05-01 04:06:00+00" network-id="2")
message-(schema="chats" id="23" chat-id="4" sender-id="4" text="I'll start on the integration tests today." sent-at="2026-05-01 04:12:00+00" updated-at="2026-05-01 04:12:00+00" network-id="2")
message-(schema="chats" id="24" chat-id="4" sender-id="6" text="Great. I'm finishing up the onboarding screens." sent-at="2026-05-01 04:30:00+00" updated-at="2026-05-01 04:30:00+00" network-id="2")
message-(schema="chats" id="25" chat-id="4" sender-id="8" text="Can we do a quick sync at 2pm?" sent-at="2026-05-01 10:00:00+00" updated-at="2026-05-01 10:00:00+00" network-id="2")
message-(schema="chats" id="26" chat-id="4" sender-id="4" text="Works for me." sent-at="2026-05-01 10:03:00+00" updated-at="2026-05-01 10:03:00+00" network-id="2")
message-(schema="chats" id="27" chat-id="4" sender-id="7" text="Same" sent-at="2026-05-01 10:06:00+00" updated-at="2026-05-01 10:06:00+00" network-id="2")
message-(schema="chats" id="28" chat-id="4" sender-id="6" text="I'll send the invite." sent-at="2026-05-01 10:09:00+00" updated-at="2026-05-01 10:09:00+00" network-id="2")
message-(schema="chats" id="29" chat-id="4" sender-id="6" text="I've updated the Figma file with the new flows" sent-at="2026-05-01 11:51:00+00" updated-at="2026-05-01 11:51:00+00" network-id="2")
message-(schema="chats" id="30" chat-id="5" sender-id="9" text="Hey, sharing the first round of designs for the settings page." sent-at="2026-04-30 06:00:00+00" updated-at="2026-04-30 06:00:00+00" network-id="2")
message-(schema="chats" id="31" chat-id="5" sender-id="6" text="These look great! Love the card layout." sent-at="2026-04-30 06:30:00+00" updated-at="2026-04-30 06:30:00+00" network-id="2")
message-(schema="chats" id="32" chat-id="5" sender-id="4" text="Agreed. One thought — the spacing on the form feels a bit tight." sent-at="2026-04-30 07:00:00+00" updated-at="2026-04-30 07:00:00+00" network-id="2")
message-(schema="chats" id="33" chat-id="5" sender-id="9" text="Good call. I'll loosen it up." sent-at="2026-04-30 07:12:00+00" updated-at="2026-04-30 07:12:00+00" network-id="2")
message-(schema="chats" id="34" chat-id="5" sender-id="6" text="Also maybe we increase the font size slightly?" sent-at="2026-04-30 08:00:00+00" updated-at="2026-04-30 08:00:00+00" network-id="2")
message-(schema="chats" id="35" chat-id="5" sender-id="9" text="The contrast on mobile looks off — can we bump it?" sent-at="2026-04-30 10:00:00+00" updated-at="2026-04-30 10:00:00+00" network-id="2")
message-(schema="chats" id="36" chat-id="6" sender-id="8" text="Good morning team! Reminder: all-hands is Thursday at 10am." sent-at="2026-05-01 03:00:00+00" updated-at="2026-05-01 03:00:00+00" network-id="2")
message-(schema="chats" id="37" chat-id="6" sender-id="6" text="Thanks for the reminder!" sent-at="2026-05-01 03:06:00+00" updated-at="2026-05-01 03:06:00+00" network-id="2")
message-(schema="chats" id="38" chat-id="6" sender-id="9" text="Will there be a recording for those in other time zones?" sent-at="2026-05-01 03:18:00+00" updated-at="2026-05-01 03:18:00+00" network-id="2")
message-(schema="chats" id="39" chat-id="6" sender-id="8" text="Yes — I'll post the link in #announcements after." sent-at="2026-05-01 03:24:00+00" updated-at="2026-05-01 03:24:00+00" network-id="2")
message-(schema="chats" id="40" chat-id="6" sender-id="4" text="Thanks Priya 🙏" sent-at="2026-05-01 03:30:00+00" updated-at="2026-05-01 03:30:00+00" network-id="2")
message-(schema="chats" id="41" chat-id="6" sender-id="7" text="Staging is back up btw, had a brief outage this morning." sent-at="2026-05-01 08:00:00+00" updated-at="2026-05-01 08:00:00+00" network-id="2")
message-(schema="chats" id="42" chat-id="6" sender-id="6" text="Oh I didn't even notice, nice quick fix!" sent-at="2026-05-01 08:12:00+00" updated-at="2026-05-01 08:12:00+00" network-id="2")
message-(schema="chats" id="43" chat-id="6" sender-id="7" text="Just pushed the hotfix to production" sent-at="2026-05-01 11:55:00+00" updated-at="2026-05-01 11:55:00+00" network-id="2")
message-(schema="chats" id="44" chat-id="7" sender-id="7" text="Heads up: I'm updating the CI pipeline today. Builds might be slow for a bit." sent-at="2026-05-01 07:00:00+00" updated-at="2026-05-01 07:00:00+00" network-id="2")
message-(schema="chats" id="45" chat-id="7" sender-id="4" text="Noted, thanks for the warning." sent-at="2026-05-01 07:06:00+00" updated-at="2026-05-01 07:06:00+00" network-id="2")
message-(schema="chats" id="46" chat-id="7" sender-id="7" text="Back to normal now." sent-at="2026-05-01 08:00:00+00" updated-at="2026-05-01 08:00:00+00" network-id="2")
message-(schema="chats" id="47" chat-id="7" sender-id="4" text="PR is up: #247 — adds rate limiting to the auth routes" sent-at="2026-05-01 09:00:00+00" updated-at="2026-05-01 09:00:00+00" network-id="2")
message-(schema="chats" id="48" chat-id="8" sender-id="8" text="Welcome to the team, Jordan! 🎉 Jordan joins us as a Product Designer." sent-at="2026-04-28 12:00:00+00" updated-at="2026-04-28 12:00:00+00" network-id="2")
message-(schema="chats" id="49" chat-id="8" sender-id="8" text="Reminder: expense reports for March are due this Friday." sent-at="2026-04-29 12:00:00+00" updated-at="2026-04-29 12:00:00+00" network-id="2")
message-(schema="chats" id="50" chat-id="8" sender-id="8" text="Q2 planning kick-off is next Monday at 9am. Please come prepared with your team's priorities." sent-at="2026-05-01 00:00:00+00" updated-at="2026-05-01 00:00:00+00" network-id="2")

336
priv/experiments/forms.sql Normal file
View File

@@ -0,0 +1,336 @@
-- members
INSERT INTO members (email, first_name, last_name, password, address1, address2, city, state, zipcode, country, county, phone, title, bio, image_path, notes, created, updated_at) VALUES
('samrussell99@pm.me', 'Sam', 'Russell', '$argon2id$v=19$m=65536,t=3,p=4$n/8BaBisEnBaQNbkxzs1VA$dvvnupWNtB5w5qTBgEciDsNA6rOgXaEypcEK1A0ndLM', '1234 address NW 12th St', NULL, 'Austin', 'Texas', '12345', 'US', 'Austin County', '123-456-789', 'CEO', 'This is my bio', NULL, 'no notes', '2026-01-15 09:58:01.0072', '2026-01-15 09:58:01.0072'),
('freddyjkrueger@gmail.com', 'Freddy','Krueger', '$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM', '1234 address NW 12th St', NULL, 'Austin', 'Texas', '12345', 'US', 'Austin County', '123-456-789', 'Villain', 'This is my bio', NULL, 'no notes', '2026-01-13 13:38:46.0810', '2026-01-13 13:38:46.0810'),
('harmysmarmy@gmail.com', 'Harmy','Smarmy', '$argon2id$v=19$m=65536,t=3,p=4$FAhGtCtqNAQ19tBYD73wXQ$0AM/khyBFFuX2mv0ieqtGfsXRgtEldWKFwyeV3BA3Xk', '1234 address NW 12th St', NULL, 'Austin', 'Texas', '12345', 'US', 'Austin County', '123-456-789', 'Associate', 'This is my bio', NULL, 'no notes', '2026-01-13 13:41:41.0722', '2026-01-13 13:41:41.0722'),
('matiascarulli@gmail.com', 'Matias', 'Carulli', '$argon2id$v=19$m=65536,t=3,p=4$U2FsdGVkc3M$08SY0x7DUPwTV12ZckHdNg', '1234 address NW 12th St', NULL, 'Miramar', 'Florida', '33029', 'US', 'Broward', '123-456-789', 'Developer', 'This is my bio', '/db/images/users/member-4/profile.png', 'no notes', '2026-03-15 13:41:41.0722', '2026-03-15 13:41:41.0722'),
('boulder@example.com', 'CU', 'Boulder', '$argon2id$v=19$m=65536,t=3,p=4$CQwOYXNwwsLBP1s/zcZNJg$OM/wwVP5U+QUnAEDKAjk5mpvujpOzpT0XkouDcmHT8E', '1234 address NW 12th St', NULL, 'Austin', 'Texas', '12345', 'US', 'Austin County', '123-456-789', 'Associate', 'This is my bio', NULL, 'no notes', '2026-03-26 01:03:18.803016+00', '2026-03-26 01:03:18.803016+00');
-- networks
INSERT INTO networks (name, logo, abbreviation, stripe_account_id, created) VALUES
('Captured Sun', 'cs.svg', 'cs', 'acct_1Sn6DwLpyskwAml9', '2026-01-10 09:58:01.0074'),
('Hyperia', 'hyperia.svg', 'hyperia', 'acct_1S4w0GHZemeF9CKR', '2026-01-10 09:58:01.0074');
-- apps
INSERT INTO apps (name) VALUES
('settings'),
('people'),
('calendar'),
('treasury'),
('politics'),
('files'),
('jobs'),
('tasks'),
('chat'),
('announcements');
INSERT INTO roles (network_id, name, is_default) VALUES
(1, 'admin', false),
(1, 'member', true),
(2, 'admin', false),
(2, 'member', true);
-- network_apps
INSERT INTO role_apps (role_id, app_id) VALUES
(1, 1),
(1, 2),
(1, 3),
(1, 4),
(1, 5),
(1, 6),
(1, 7),
(1, 8),
(1, 9),
(1, 10),
(3, 1),
(3, 2),
(3, 3),
(3, 4),
(3, 5),
(3, 6),
(3, 7),
(3, 8),
(3, 9),
(3, 10);
-- member_networks
INSERT INTO member_networks (member_id, network_id, created) VALUES
(1, 1, '2025-11-24 00:54:36.0784'),
(2, 1, '2026-01-13 13:14:28.0178'),
(3, 1, '2026-01-13 13:28:35.0701'),
(1, 2, '2026-01-13 13:28:35.0701'),
(4, 2, '2026-03-15 13:28:35.0701');
-- network_plans
INSERT INTO network_plans (id, network_id, stripe_price_id, name, price, description, active, created) VALUES
(1, 1, 'price_1T3uaxLpyskwAml9p0r0nh2h', 'Patron Membership', 200.00, 'Members 40+', true, '2026-03-29 22:14:45.414163'),
(2, 1, 'price_1T3uaQLpyskwAml9rZAKBcy0', 'Regular Membership', 100.00, 'Members 18-40', true, '2026-03-29 22:14:45.414163');
-- join form seed data
INSERT INTO org_1.join_form (fname, lname, email, phone, county, time) VALUES
('James', 'Mitchell', 'james.mitchell@gmail.com', '512-555-0101', 'Comal', '2025-12-16 23:11:31.0011'),
('Rachel', 'Torres', 'rachel.torres@yahoo.com', '512-555-0102', 'Bexar', '2025-12-19 19:23:12.0717'),
('David', 'Nguyen', 'david.nguyen@gmail.com', '830-555-0103', 'Comal', '2026-01-06 16:55:29.0288'),
('Emily', 'Sanders', 'emily.sanders@outlook.com', '210-555-0104', 'Hays', '2026-01-07 17:14:01.0711');
-- contact form seed data
INSERT INTO org_1.contact_form (fname, lname, email, phone, county, message, time) VALUES
('Marcus', 'Webb', 'marcus.webb@gmail.com', '512-555-0201', 'Comal', 'Interested in volunteering at upcoming events.', '2025-12-29 13:20:28.0157'),
('Sandra', 'Holloway', 'sandra.holloway@gmail.com', '830-555-0202', 'Comal', 'Would love to connect with your organization.', '2025-12-30 22:10:24.0971'),
('Robert', 'Finley', 'robert.finley@gmail.com', '210-555-0203', 'Comal', 'Looking forward to getting more involved locally.', '2026-01-10 21:23:51.0073'),
('Barbara', 'Crane', 'barbara.crane@outlook.com', '512-555-0204', 'Comal', 'Please reach out regarding the next meeting schedule.', '2026-01-10 21:23:54.0841');
-- calendars table seed data
INSERT INTO events.calendars (network_id, owner_id, name, description, color) VALUES
(2, 1, 'Main Calendar', 'The main calendar for the network', '#9E1C29'),
(2, 1, 'Sub-Calendar', 'Sub-calendar for the network', '#3D6FAD'),
(2, 1, 'Sub-Calendar 2', 'Another sub-calendar for the network', '#2A8636');
-- events table seed data
INSERT INTO events.events (network_id, creator_id, title, description, time_start, time_end, location, all_day) VALUES
(2, 1, 'Client meeting', 'Meeting with big client for app deployment', '2026-04-01T17:30:00.000Z', '2026-04-01T19:00:00.000Z', 'Virtual', false),
(2, 1, 'Networking Event', 'Networking event for young professionals', '2026-04-04T04:00:00.000Z', '2026-04-06T04:00:00.000Z', 'GB Center', true),
(2, 1, 'App deployment party', 'Come celebrate the app deployment!', '2026-04-05T16:00:00.000Z', '2026-04-05T21:30:00.000Z', 'Captured Sun HQ', false),
(2, 4, 'Work on frontend changes', 'Reminder for work #1', '2026-04-02T15:00:00.000Z', '2026-04-02T19:00:00.000Z', null, false),
(2, 4, 'Day off', 'I dont have to work today', '2026-04-03T04:00:00.000Z', '2026-04-03T04:00:00.000Z', null, true),
(2, 4, 'Scrum Meeting - Main Team', 'Agile Week 32', '2026-03-31T03:00:00.000Z', '2026-03-31T06:15:00.000Z', 'Virtual', false),
(2, 1, 'Meeting with John Smiith', 'Lorem ipsum elorum', '2026-03-31T18:30:00.000Z', '2026-03-31T19:30:00.000Z', 'Virtual', false),
(2, 1, 'Meeting with Jane Doe', 'Lorem ipsum elorum', '2026-03-31T19:30:00.000Z', '2026-03-31T19:45:00.000Z', 'Virtual', false),
(2, 1, 'Meeting with president', 'Lorem ipsum elorum', '2026-03-31T19:45:00.000Z', '2026-03-31T21:15:00.000Z', 'Virtual', false),
(2, 1, 'Meeting with investors', 'Lorem ipsum elorum', '2026-03-31T21:30:00.000Z', '2026-03-31T23:00:00.000Z', 'Virtual', false),
(2, 1, 'Review Github changes', 'Review pushes from members to ensure all is well', '2026-04-03T04:00:00.000Z', '2026-04-03T04:00:00.000Z', null, true);
INSERT INTO events.event_calendars (event_id, calendar_id) VALUES
(1, 1),
(2, 2),
(3, 1),
(3, 3),
(4, 2),
(5, 2),
(6, 1),
(6, 2),
(7, 3),
(8, 2),
(8, 3),
(9, 3),
(10, 3),
(11, 3);
-- jobs table seed data
INSERT INTO public.jobs (network_id, creator_id, title, company, location, employment_type, experience_level, department, salary_number, salary_period, applicants, skills, description, created, updated_at) VALUES
(2, 1, 'Senior Frontend Engineer', 'Acme Corp', 'San Francisco, CA', 'full-time', 'senior', 'Engineering', 165000, 'year', 47, ARRAY['React', 'TypeScript', 'CSS', 'GraphQL', 'Node.js'], E'We''re looking for a Senior Frontend Engineer to join our growing product team. You''ll work closely with designers, backend engineers, and product managers to build fast, accessible, and beautiful web experiences.\n\nYou''ll have ownership over large features and be expected to make architectural decisions. We ship every week and care deeply about code quality and UX.', '2026-04-29 10:00:00', '2026-04-29 10:00:00'),
(2, 1, 'Product Designer', 'Blue River', 'New York, NY', 'full-time', 'mid', 'Design', 120000, 'year', 112, ARRAY['Figma', 'Design Systems', 'Prototyping', 'User Research'], E'Blue River is hiring a Product Designer to lead design across our core consumer product. You''ll partner with PMs and engineers to take features from zero to one, establish design patterns, and advocate for users in every decision.\n\nWe''re a small, fast-moving team and designers have a huge impact here.', '2026-04-26 09:00:00', '2026-04-26 09:00:00'),
(2, 1, 'Backend Engineer', 'Orbit Systems', 'Remote', 'contract', 'mid', 'Platform', 95, 'hour', 29, ARRAY['Go', 'PostgreSQL', 'Kubernetes', 'gRPC', 'Redis'], '6-month contract (potential to extend) for a backend engineer to help scale our platform infrastructure. You''ll be responsible for building and maintaining APIs used by millions of users, optimizing database performance, and improving system reliability.', '2026-04-30 14:00:00', '2026-04-30 14:00:00'),
(2, 1, 'Marketing Manager', 'Groundwork', 'Austin, TX', 'full-time', 'mid', 'Marketing', 98000, 'year', 88, ARRAY['SEO', 'Content Strategy', 'Analytics', 'Paid Acquisition', 'Email Marketing'], E'We need a Marketing Manager to own our top-of-funnel growth. You''ll manage content, paid channels, and email campaigns — and be the person who keeps the brand consistent across every touchpoint. You''ll report directly to our Head of Growth.', '2026-04-21 11:00:00', '2026-04-21 11:00:00'),
(2, 1, 'Data Analyst', 'Compass Data', 'Chicago, IL', 'full-time', 'entry', 'Analytics', 75000, 'year', 203, ARRAY['SQL', 'Python', 'Tableau', 'dbt', 'Excel'], 'A great entry-level opportunity for someone who loves data. You''ll work with our analytics team to build dashboards, run ad-hoc analysis, and help business teams make data-driven decisions. We''ll invest in your growth and give you plenty of mentorship.', '2026-04-28 08:30:00', '2026-04-28 08:30:00'),
(2, 1, 'iOS Engineer', 'Fieldwork', 'Seattle, WA', 'full-time', 'senior', 'Mobile', 175000, 'year', 34, ARRAY['Swift', 'SwiftUI', 'Combine', 'CoreData', 'Xcode'], E'Fieldwork is building next-generation tools for field service teams. Our iOS app is the most important surface we have — technicians use it every day on job sites. We need a senior iOS engineer who cares about performance, offline reliability, and a great UX.', '2026-04-24 13:00:00', '2026-04-24 13:00:00'),
(2, 1, 'Operations Coordinator', 'Maple & Co', 'Boston, MA', 'part-time', 'entry', 'Operations', 28, 'hour', 61, ARRAY['Project Management', 'Excel', 'Communication', 'Scheduling'], E'Part-time role (20 hrs/week) helping our operations team stay organized. You''ll coordinate schedules, manage vendor relationships, and help improve internal workflows. Ideal for someone who''s highly organized and excited to grow into a full-time ops role.', '2026-04-17 10:00:00', '2026-04-17 10:00:00'),
(2, 1, 'Machine Learning Intern', 'NeuralPath', 'Remote', 'internship', 'entry', 'AI Research', 8000, 'month', 394, ARRAY['Python', 'PyTorch', 'Linear Algebra', 'Git'], 'Summer internship (12 weeks) on our ML research team. You''ll work alongside researchers on real problems in NLP and recommendation systems. We expect you to ship something you''re proud of by the end of the summer. Strong preference for candidates who can start June 2.', '2026-05-01 09:00:00', '2026-05-01 09:00:00');
INSERT INTO permissions (key, app_id, description) VALUES
('events.add', 3, 'Can add events'),
('events.delete', 3, 'Can delete events'),
('events.edit', 3, 'Can edit events'),
('events.get', 3, 'Can view events'),
('jobs.add', 7, 'Can add jobs'),
('jobs.delete', 7, 'Can delete jobs'),
('jobs.edit', 7, 'Can edit jobs'),
('jobs.get', 7, 'Can view jobs'),
('announcements.add', 10, 'Can add announcements'),
('announcements.delete', 10, 'Can delete announcements'),
('announcements.edit', 10, 'Can edit announcements'),
('announcements.get', 10, 'Can view announcements'),
('role_apps.edit', 1, 'Can edit role apps'),
('roles.create', 1, 'Can create roles'),
('roles.delete', 1, 'Can delete roles'),
('role_notifications.edit', 1, 'Can edit role notifications'),
('chats.create', 9, 'Can create chats'),
('chats.edit', 9, 'Can edit chats'),
('chats.delete', 9, 'Can delete chats'),
('chats_message.send', 9, 'Can send messages'),
('chats_message.edit', 9, 'Can edit messages'),
('chats_message.delete', 9, 'Can delete messages');
INSERT INTO role_permissions (role_id, permission_key) VALUES
(1, 'events.get'), (1, 'jobs.get'), (1, 'announcements.get'), (1, 'events.add'), (1, 'events.delete'), (1, 'events.edit'), (1, 'jobs.add'), (1, 'jobs.delete'), (1, 'jobs.edit'), (1, 'announcements.add'), (1, 'announcements.delete'), (1, 'announcements.edit'), (1, 'role_apps.edit'), (1, 'roles.create'), (1, 'roles.delete'), (1, 'chats.create'), (1, 'chats.edit'), (1, 'chats.delete'), (1, 'chats_message.send'), (1, 'chats_message.edit'), (1, 'chats_message.delete'), (1, 'role_notifications.edit'),-- network 1 admin
(2, 'events.get'), (2, 'jobs.get'), (2, 'announcements.get'), (2, 'events.add'), (2, 'chats.create'), (2, 'chats_message.send'), -- network 1 user
(3, 'events.get'), (3, 'jobs.get'), (3, 'announcements.get'), (3, 'events.add'), (3, 'events.delete'), (3, 'events.edit'), (3, 'jobs.add'), (3, 'jobs.delete'), (3, 'jobs.edit'), (3, 'announcements.add'), (3, 'announcements.delete'), (3, 'announcements.edit'), (3, 'role_apps.edit'), (3, 'roles.create'), (3, 'roles.delete'), (3, 'chats.create'), (3, 'chats.edit'), (3, 'chats.delete'), (3, 'chats_message.send'), (3, 'chats_message.edit'), (3, 'chats_message.delete'), (3, 'role_notifications.edit'),-- network 2 admin
(4, 'events.get'), (4, 'jobs.get'), (4, 'announcements.get'), (4, 'events.add'), (4, 'chats.create'), (4, 'chats_message.send'); -- network 2 user
INSERT INTO member_roles (member_id, role_id, granted_by) VALUES
(1, 1, 1), (2, 2, 1), (3, 2, 1), -- network 1
(1, 3, 1), (4, 3, 1), (6, 3, 1); -- network 2
INSERT INTO announcements (network_id, creator_id, text, created, updated_at) VALUES
(2, 1, 'This is the first announcement', '2026-01-13 15:37:00.0000', '2026-01-13 15:37:00.0000'),
(2, 1, 'Here goes another announcement.', '2026-01-13 15:39:00.0000', '2026-01-13 15:39:00.0000'),
(2, 4, 'My first announcement!', '2026-01-14 15:41:00.0000', '2026-01-14 15:41:00.0000'),
(2, 1, 'Testing announcement.', '2026-02-17 12:30:00.0000', '2026-02-17 12:30:00.0000'),
(2, 4, 'Quick fire!', '2026-03-23 15:56:00.0000', '2026-03-23 15:56:00.0000'),
(2, 4, 'Quick fire!', '2026-03-23 15:56:30.0000', '2026-03-23 15:56:30.0000'),
(2, 4, 'Quick fire!', '2026-03-23 15:57:00.0000', '2026-03-23 15:57:00.0000'),
(2, 1, 'Trying another user.', '2026-02-05 15:56:30.0000', '2026-02-05 15:56:30.0000'),
(2, 1, 'One last announcement.', '2026-04-01 15:57:00.0000', '2026-01-13 15:57:00.0000');
INSERT INTO public.join_code (code, network_id) VALUES
('cs', 1),
('hyperia', 2);
-- Recurrence rules for 3 seed events (0=Sun, 1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri, 6=Sat)
INSERT INTO events.event_recurrence (frequency, interval, days_of_week, end_date, count) VALUES
('weekly', 1, '{2}', null, null), -- id=1: every Tuesday → Scrum Meeting
('weekly', 2, '{3}', null, 10 ), -- id=2: every 2nd Wednesday x10 → Client Meeting
('weekly', 1, '{1,3,5}', null, null); -- id=3: every Mon/Wed/Fri → Review Github
UPDATE events.events SET recurrence_id = 1 WHERE id = 6;
UPDATE events.events SET recurrence_id = 2 WHERE id = 1;
UPDATE events.events SET recurrence_id = 3 WHERE id = 11;
ALTER TABLE public.files ALTER COLUMN type TYPE varchar(100);
-- chat seed members (Sarah McIntyre=6, Marcus Webb=7, Priya Anand=8, Jordan Kim=9)
INSERT INTO members (email, first_name, last_name, password, address1, address2, city, state, zipcode, country, county, phone, title, bio, image_path, notes, created, updated_at) VALUES
('sarah.mcintyre@example.com', 'Sarah', 'McIntyre', '$argon2id$v=19$m=65536,t=3,p=4$U2FsdGVkc3M$08SY0x7DUPwTV12ZckHdNg', '1234 Address St', NULL, 'Austin', 'Texas', '12345', 'US', 'Travis', '123-456-789', 'Designer', 'This is my bio', NULL, NULL, '2026-02-01 09:00:00+00', '2026-02-01 09:00:00+00'),
('marcus.webb@example.com', 'Marcus', 'Webb', '$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM', '1234 Address St', NULL, 'Austin', 'Texas', '12345', 'US', 'Travis', '123-456-789', 'Engineer', 'This is my bio', NULL, NULL, '2026-02-01 09:00:00+00', '2026-02-01 09:00:00+00'),
('priya.anand@example.com', 'Priya', 'Anand', '$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM', '1234 Address St', NULL, 'Austin', 'Texas', '12345', 'US', 'Travis', '123-456-789', 'PM', 'This is my bio', NULL, NULL, '2026-02-01 09:00:00+00', '2026-02-01 09:00:00+00'),
('jordan.kim@example.com', 'Jordan', 'Kim', '$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM', '1234 Address St', NULL, 'Austin', 'Texas', '12345', 'US', 'Travis', '123-456-789', 'Designer', 'This is my bio', NULL, NULL, '2026-02-01 09:00:00+00', '2026-02-01 09:00:00+00');
INSERT INTO member_networks (member_id, network_id, created) VALUES
(6, 2, '2026-02-01 09:00:00+00'),
(7, 2, '2026-02-01 09:00:00+00'),
(8, 2, '2026-02-01 09:00:00+00'),
(9, 2, '2026-02-01 09:00:00+00');
INSERT INTO member_roles (member_id, role_id, granted_by) VALUES
(6, 4, 1),
(7, 4, 1),
(8, 4, 1),
(9, 4, 1);
-- chats (network 2 / Hyperia)
-- updated_at reflects the last message's sent_at for each chat
INSERT INTO chats.chats (network_id, creator_id, type, name, created, updated_at) VALUES
(2, 4, 'dm', NULL, '2026-04-01 09:00:00+00', '2026-05-01 11:42:00+00'), -- 1: DM Matias↔Sarah
(2, 4, 'dm', NULL, '2026-04-01 09:00:00+00', '2026-05-01 10:30:00+00'), -- 2: DM Matias↔Marcus
(2, 4, 'dm', NULL, '2026-04-01 09:00:00+00', '2026-04-30 18:00:00+00'), -- 3: DM Matias↔Priya
(2, 4, 'group', 'Product Team', '2026-04-01 09:00:00+00', '2026-05-01 11:51:00+00'), -- 4
(2, 4, 'group', 'Design Review', '2026-04-01 09:00:00+00', '2026-04-30 10:00:00+00'), -- 5
(2, 4, 'channel', 'general', '2026-04-01 09:00:00+00', '2026-05-01 11:55:00+00'), -- 6
(2, 4, 'channel', 'engineering', '2026-04-01 09:00:00+00', '2026-05-01 09:00:00+00'), -- 7
(2, 8, 'announcement', 'Announcements', '2026-04-01 09:00:00+00', '2026-05-01 00:00:00+00'); -- 8
-- chat_members
-- last_read_at for member 4 (Matias) is set to match the unread counts from the hardcoded data
INSERT INTO chats.chat_members (chat_id, member_id, last_read_at, joined_at) VALUES
-- chat 1: DM Sarah (unread=2 → Matias last read before the final 2 messages from Sarah)
(1, 4, '2026-05-01 07:12:00+00', '2026-04-01 09:00:00+00'),
(1, 6, NULL, '2026-04-01 09:00:00+00'),
-- chat 2: DM Marcus (unread=0)
(2, 4, '2026-05-01 10:30:00+00', '2026-04-01 09:00:00+00'),
(2, 7, NULL, '2026-04-01 09:00:00+00'),
-- chat 3: DM Priya (unread=0)
(3, 4, '2026-04-30 18:00:00+00', '2026-04-01 09:00:00+00'),
(3, 8, NULL, '2026-04-01 09:00:00+00'),
-- chat 4: Product Team (unread=5 → Matias last read after the 4th message)
(4, 4, '2026-05-01 04:30:00+00', '2026-04-01 09:00:00+00'),
(4, 6, NULL, '2026-04-01 09:00:00+00'),
(4, 7, NULL, '2026-04-01 09:00:00+00'),
(4, 8, NULL, '2026-04-01 09:00:00+00'),
-- chat 5: Design Review (unread=0)
(5, 4, '2026-04-30 10:00:00+00', '2026-04-01 09:00:00+00'),
(5, 6, NULL, '2026-04-01 09:00:00+00'),
(5, 9, NULL, '2026-04-01 09:00:00+00'),
-- chat 6: #general (unread=11 → all unread, NULL)
(6, 1, NULL, '2026-04-01 09:00:00+00'),
(6, 4, NULL, '2026-04-01 09:00:00+00'),
(6, 6, NULL, '2026-04-01 09:00:00+00'),
(6, 7, NULL, '2026-04-01 09:00:00+00'),
(6, 8, NULL, '2026-04-01 09:00:00+00'),
(6, 9, NULL, '2026-04-01 09:00:00+00'),
-- chat 7: #engineering (unread=0)
(7, 1, NULL, '2026-04-01 09:00:00+00'),
(7, 4, '2026-05-01 09:00:00+00', '2026-04-01 09:00:00+00'),
(7, 7, NULL, '2026-04-01 09:00:00+00'),
-- chat 8: Announcements (unread=1 → Matias last read after the 2nd message)
(8, 1, NULL, '2026-04-01 09:00:00+00'),
(8, 4, '2026-04-29 12:00:00+00', '2026-04-01 09:00:00+00'),
(8, 6, NULL, '2026-04-01 09:00:00+00'),
(8, 7, NULL, '2026-04-01 09:00:00+00'),
(8, 8, NULL, '2026-04-01 09:00:00+00'),
(8, 9, NULL, '2026-04-01 09:00:00+00');
-- messages
-- Timestamps computed from "now" = 2026-05-01 12:00:00+00, matching ago(h) from the hardcoded constructor
-- sender_id mapping: Matias=4, Sarah=6, Marcus=7, Priya=8, Jordan=9
INSERT INTO chats.messages (chat_id, sender_id, text, sent_at, updated_at) VALUES
-- chat 1: DM Matias↔Sarah
(1, 4, 'Hey Sarah, did you see the new design mockups?', '2026-04-30 12:00:00+00', '2026-04-30 12:00:00+00'),
(1, 6, 'Just looked — they''re really clean. I love the new sidebar.','2026-04-30 12:30:00+00', '2026-04-30 12:30:00+00'),
(1, 4, 'Agreed. Alex did a great job.', '2026-04-30 12:36:00+00', '2026-04-30 12:36:00+00'),
(1, 6, 'Are we going to ship this week or wait for the backend?', '2026-05-01 07:00:00+00', '2026-05-01 07:00:00+00'),
(1, 4, 'Let''s aim for Thursday. I''ll sync with Marcus.', '2026-05-01 07:12:00+00', '2026-05-01 07:12:00+00'),
(1, 6, 'Sounds good 👍', '2026-05-01 07:18:00+00', '2026-05-01 07:18:00+00'),
(1, 6, 'Can you review the PR when you get a chance?', '2026-05-01 11:42:00+00', '2026-05-01 11:42:00+00'),
-- chat 2: DM Matias↔Marcus
(2, 7, 'Hey, the API endpoint is returning 500s on staging.', '2026-05-01 09:00:00+00', '2026-05-01 09:00:00+00'),
(2, 4, 'Oh no — is it the auth middleware again?', '2026-05-01 09:06:00+00', '2026-05-01 09:06:00+00'),
(2, 7, 'Yep. Same issue as last week.', '2026-05-01 09:12:00+00', '2026-05-01 09:12:00+00'),
(2, 4, 'I''ll patch it now. Give me 20 mins.', '2026-05-01 09:15:00+00', '2026-05-01 09:15:00+00'),
(2, 7, 'Thanks, no rush.', '2026-05-01 09:18:00+00', '2026-05-01 09:18:00+00'),
(2, 4, 'Fixed. Can you redeploy and check?', '2026-05-01 09:48:00+00', '2026-05-01 09:48:00+00'),
(2, 7, 'All green 🎉 Thanks!', '2026-05-01 09:54:00+00', '2026-05-01 09:54:00+00'),
(2, 4, 'I''ll send over the specs by EOD', '2026-05-01 10:30:00+00', '2026-05-01 10:30:00+00'),
-- chat 3: DM Matias↔Priya
(3, 8, 'Quick question — what''s the launch date for v2?', '2026-04-30 16:00:00+00', '2026-04-30 16:00:00+00'),
(3, 4, 'Still TBD, but we''re targeting end of May.', '2026-04-30 16:12:00+00', '2026-04-30 16:12:00+00'),
(3, 8, 'Got it. I''ll update the roadmap doc.', '2026-04-30 16:30:00+00', '2026-04-30 16:30:00+00'),
(3, 4, 'Perfect, thanks Priya.', '2026-04-30 16:36:00+00', '2026-04-30 16:36:00+00'),
(3, 8, 'See you at the standup!', '2026-04-30 18:00:00+00', '2026-04-30 18:00:00+00'),
-- chat 4: Product Team
(4, 7, 'Morning everyone! API docs are updated.', '2026-05-01 04:00:00+00', '2026-05-01 04:00:00+00'),
(4, 8, 'Nice work Marcus 🙌', '2026-05-01 04:06:00+00', '2026-05-01 04:06:00+00'),
(4, 4, 'I''ll start on the integration tests today.', '2026-05-01 04:12:00+00', '2026-05-01 04:12:00+00'),
(4, 6, 'Great. I''m finishing up the onboarding screens.', '2026-05-01 04:30:00+00', '2026-05-01 04:30:00+00'),
(4, 8, 'Can we do a quick sync at 2pm?', '2026-05-01 10:00:00+00', '2026-05-01 10:00:00+00'),
(4, 4, 'Works for me.', '2026-05-01 10:03:00+00', '2026-05-01 10:03:00+00'),
(4, 7, 'Same', '2026-05-01 10:06:00+00', '2026-05-01 10:06:00+00'),
(4, 6, 'I''ll send the invite.', '2026-05-01 10:09:00+00', '2026-05-01 10:09:00+00'),
(4, 6, 'I''ve updated the Figma file with the new flows', '2026-05-01 11:51:00+00', '2026-05-01 11:51:00+00'),
-- chat 5: Design Review
(5, 9, 'Hey, sharing the first round of designs for the settings page.', '2026-04-30 06:00:00+00', '2026-04-30 06:00:00+00'),
(5, 6, 'These look great! Love the card layout.', '2026-04-30 06:30:00+00', '2026-04-30 06:30:00+00'),
(5, 4, 'Agreed. One thought — the spacing on the form feels a bit tight.', '2026-04-30 07:00:00+00', '2026-04-30 07:00:00+00'),
(5, 9, 'Good call. I''ll loosen it up.', '2026-04-30 07:12:00+00', '2026-04-30 07:12:00+00'),
(5, 6, 'Also maybe we increase the font size slightly?', '2026-04-30 08:00:00+00', '2026-04-30 08:00:00+00'),
(5, 9, 'The contrast on mobile looks off — can we bump it?', '2026-04-30 10:00:00+00', '2026-04-30 10:00:00+00'),
-- chat 6: #general
(6, 8, 'Good morning team! Reminder: all-hands is Thursday at 10am.', '2026-05-01 03:00:00+00', '2026-05-01 03:00:00+00'),
(6, 6, 'Thanks for the reminder!', '2026-05-01 03:06:00+00', '2026-05-01 03:06:00+00'),
(6, 9, 'Will there be a recording for those in other time zones?', '2026-05-01 03:18:00+00', '2026-05-01 03:18:00+00'),
(6, 8, 'Yes — I''ll post the link in #announcements after.', '2026-05-01 03:24:00+00', '2026-05-01 03:24:00+00'),
(6, 4, 'Thanks Priya 🙏', '2026-05-01 03:30:00+00', '2026-05-01 03:30:00+00'),
(6, 7, 'Staging is back up btw, had a brief outage this morning.', '2026-05-01 08:00:00+00', '2026-05-01 08:00:00+00'),
(6, 6, 'Oh I didn''t even notice, nice quick fix!', '2026-05-01 08:12:00+00', '2026-05-01 08:12:00+00'),
(6, 7, 'Just pushed the hotfix to production', '2026-05-01 11:55:00+00', '2026-05-01 11:55:00+00'),
-- chat 7: #engineering
(7, 7, 'Heads up: I''m updating the CI pipeline today. Builds might be slow for a bit.', '2026-05-01 07:00:00+00', '2026-05-01 07:00:00+00'),
(7, 4, 'Noted, thanks for the warning.', '2026-05-01 07:06:00+00', '2026-05-01 07:06:00+00'),
(7, 7, 'Back to normal now.', '2026-05-01 08:00:00+00', '2026-05-01 08:00:00+00'),
(7, 4, 'PR is up: #247 — adds rate limiting to the auth routes', '2026-05-01 09:00:00+00', '2026-05-01 09:00:00+00'),
-- chat 8: Announcements
(8, 8, 'Welcome to the team, Jordan! 🎉 Jordan joins us as a Product Designer.', '2026-04-28 12:00:00+00', '2026-04-28 12:00:00+00'),
(8, 8, 'Reminder: expense reports for March are due this Friday.', '2026-04-29 12:00:00+00', '2026-04-29 12:00:00+00'),
(8, 8, 'Q2 planning kick-off is next Monday at 9am. Please come prepared with your team''s priorities.', '2026-05-01 00:00:00+00', '2026-05-01 00:00:00+00');
-- member_apps
INSERT INTO member_apps (member_id, app_id) VALUES
(1, 1), (4, 1), (6, 1),
(1, 2), (4, 2), (6, 2),
(1, 3), (4, 3), (6, 3),
(1, 4), (4, 4), (6, 4),
(1, 5), (4, 5), (6, 5),
(1, 6), (4, 6), (6, 6),
(1, 7), (4, 7), (6, 7),
(1, 8), (4, 8), (6, 8),
(1, 9), (4, 10), (6, 9);

72
priv/forms.html Normal file
View File

@@ -0,0 +1,72 @@
<forms->
<!-- global -->
<member- id="1" email="samrussell99@pm.me" first-name="Sam" last-name="Russell" password="$argon2id$v=19$m=65536,t=3,p=4$n/8BaBisEnBaQNbkxzs1VA$dvvnupWNtB5w5qTBgEciDsNA6rOgXaEypcEK1A0ndLM" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="CEO" bio="This is my bio" notes="no notes" created="2026-01-15 09:58:01.0072" updated-at="2026-01-15 09:58:01.0072"></member->
<member- id="2" email="freddyjkrueger@gmail.com" first-name="Freddy" last-name="Krueger" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="Villain" bio="This is my bio" notes="no notes" created="2026-01-13 13:38:46.0810" updated-at="2026-01-13 13:38:46.0810"></member->
<member- id="3" email="harmysmarmy@gmail.com" first-name="Harmy" last-name="Smarmy" password="$argon2id$v=19$m=65536,t=3,p=4$FAhGtCtqNAQ19tBYD73wXQ$0AM/khyBFFuX2mv0ieqtGfsXRgtEldWKFwyeV3BA3Xk" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="Associate" bio="This is my bio" notes="no notes" created="2026-01-13 13:41:41.0722" updated-at="2026-01-13 13:41:41.0722"></member->
<member- id="4" email="matiascarulli@gmail.com" first-name="Matias" last-name="Carulli" password="$argon2id$v=19$m=65536,t=3,p=4$U2FsdGVkc3M$08SY0x7DUPwTV12ZckHdNg" address1="1234 address NW 12th St" city="Miramar" state="Florida" zipcode="33029" country="US" county="Broward" phone="123-456-789" title="Developer" bio="This is my bio" image-path="/db/images/users/member-4/profile.png" notes="no notes" created="2026-03-15 13:41:41.0722" updated-at="2026-03-15 13:41:41.0722"></member->
<member- id="5" email="boulder@example.com" first-name="CU" last-name="Boulder" password="$argon2id$v=19$m=65536,t=3,p=4$CQwOYXNwwsLBP1s/zcZNJg$OM/wwVP5U+QUnAEDKAjk5mpvujpOzpT0XkouDcmHT8E" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="Associate" bio="This is my bio" notes="no notes" created="2026-03-26 01:03:18.803016+00" updated-at="2026-03-26 01:03:18.803016+00"></member->
<member- id="6" email="sarah.mcintyre@example.com" first-name="Sarah" last-name="McIntyre" password="$argon2id$v=19$m=65536,t=3,p=4$U2FsdGVkc3M$08SY0x7DUPwTV12ZckHdNg" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="Designer" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00"></member->
<member- id="7" email="marcus.webb@example.com" first-name="Marcus" last-name="Webb" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="Engineer" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00"></member->
<member- id="8" email="priya.anand@example.com" first-name="Priya" last-name="Anand" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="PM" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00"></member->
<member- id="9" email="jordan.kim@example.com" first-name="Jordan" last-name="Kim" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="Designer" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00"></member->
<app- id="1" name="settings"></app->
<app- id="2" name="people"></app->
<app- id="3" name="calendar"></app->
<app- id="4" name="treasury"></app->
<app- id="5" name="politics"></app->
<app- id="6" name="files"></app->
<app- id="7" name="jobs"></app->
<app- id="8" name="tasks"></app->
<app- id="9" name="chat"></app->
<app- id="10" name="announcements"></app->
<permission- id="1" key="events.add" app-id="3" description="Can add events"></permission->
<permission- id="2" key="events.delete" app-id="3" description="Can delete events"></permission->
<permission- id="3" key="events.edit" app-id="3" description="Can edit events"></permission->
<permission- id="4" key="events.get" app-id="3" description="Can view events"></permission->
<permission- id="5" key="jobs.add" app-id="7" description="Can add jobs"></permission->
<permission- id="6" key="jobs.delete" app-id="7" description="Can delete jobs"></permission->
<permission- id="7" key="jobs.edit" app-id="7" description="Can edit jobs"></permission->
<permission- id="8" key="jobs.get" app-id="7" description="Can view jobs"></permission->
<permission- id="9" key="announcements.add" app-id="10" description="Can add announcements"></permission->
<permission- id="10" key="announcements.delete" app-id="10" description="Can delete announcements"></permission->
<permission- id="11" key="announcements.edit" app-id="10" description="Can edit announcements"></permission->
<permission- id="12" key="announcements.get" app-id="10" description="Can view announcements"></permission->
<permission- id="13" key="role_apps.edit" app-id="1" description="Can edit role apps"></permission->
<permission- id="14" key="roles.create" app-id="1" description="Can create roles"></permission->
<permission- id="15" key="roles.delete" app-id="1" description="Can delete roles"></permission->
<permission- id="16" key="role_notifications.edit" app-id="1" description="Can edit role notifications"></permission->
<permission- id="17" key="chats.create" app-id="9" description="Can create chats"></permission->
<permission- id="18" key="chats.edit" app-id="9" description="Can edit chats"></permission->
<permission- id="19" key="chats.delete" app-id="9" description="Can delete chats"></permission->
<permission- id="20" key="chats_message.send" app-id="9" description="Can send messages"></permission->
<permission- id="21" key="chats_message.edit" app-id="9" description="Can edit messages"></permission->
<permission- id="22" key="chats_message.delete" app-id="9" description="Can delete messages"></permission->
<member-app- id="1" member-id="1" app-id="1"></member-app->
<member-app- id="2" member-id="4" app-id="1"></member-app->
<member-app- id="3" member-id="6" app-id="1"></member-app->
<member-app- id="4" member-id="1" app-id="2"></member-app->
<member-app- id="5" member-id="4" app-id="2"></member-app->
<member-app- id="6" member-id="6" app-id="2"></member-app->
<member-app- id="7" member-id="1" app-id="3"></member-app->
<member-app- id="8" member-id="4" app-id="3"></member-app->
<member-app- id="9" member-id="6" app-id="3"></member-app->
<member-app- id="10" member-id="1" app-id="4"></member-app->
<member-app- id="11" member-id="4" app-id="4"></member-app->
<member-app- id="12" member-id="6" app-id="4"></member-app->
<member-app- id="13" member-id="1" app-id="5"></member-app->
<member-app- id="14" member-id="4" app-id="5"></member-app->
<member-app- id="15" member-id="6" app-id="5"></member-app->
<member-app- id="16" member-id="1" app-id="6"></member-app->
<member-app- id="17" member-id="4" app-id="6"></member-app->
<member-app- id="18" member-id="6" app-id="6"></member-app->
<member-app- id="19" member-id="1" app-id="7"></member-app->
<member-app- id="20" member-id="4" app-id="7"></member-app->
<member-app- id="21" member-id="6" app-id="7"></member-app->
<member-app- id="22" member-id="1" app-id="8"></member-app->
<member-app- id="23" member-id="4" app-id="8"></member-app->
<member-app- id="24" member-id="6" app-id="8"></member-app->
<member-app- id="25" member-id="1" app-id="9"></member-app->
<member-app- id="26" member-id="4" app-id="10"></member-app->
<member-app- id="27" member-id="6" app-id="9"></member-app->
</forms->

15
priv/js/form_init.js Normal file
View File

@@ -0,0 +1,15 @@
// Generic init script for a supervised form runtime. The supervisor
// (Forum.Forms) injects two globals via QuickBEAM's :define
// option before this script runs:
//
// className: string - e.g. "Task"
// formData: object - attributes + content from the source element
//
// We build a class with the requested name that extends Form, then
// instantiate it. The instance lives for the life of this runtime.
class Form {
constructor(data) {
Object.assign(this, data);
}
}

View File

@@ -0,0 +1,264 @@
<forms->
<!-- global -->
<member- id="1" email="samrussell99@pm.me" first-name="Sam" last-name="Russell" password="$argon2id$v=19$m=65536,t=3,p=4$n/8BaBisEnBaQNbkxzs1VA$dvvnupWNtB5w5qTBgEciDsNA6rOgXaEypcEK1A0ndLM" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="CEO" bio="This is my bio" notes="no notes" created="2026-01-15 09:58:01.0072" updated-at="2026-01-15 09:58:01.0072"></member->
<member- id="2" email="freddyjkrueger@gmail.com" first-name="Freddy" last-name="Krueger" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="Villain" bio="This is my bio" notes="no notes" created="2026-01-13 13:38:46.0810" updated-at="2026-01-13 13:38:46.0810"></member->
<member- id="3" email="harmysmarmy@gmail.com" first-name="Harmy" last-name="Smarmy" password="$argon2id$v=19$m=65536,t=3,p=4$FAhGtCtqNAQ19tBYD73wXQ$0AM/khyBFFuX2mv0ieqtGfsXRgtEldWKFwyeV3BA3Xk" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="Associate" bio="This is my bio" notes="no notes" created="2026-01-13 13:41:41.0722" updated-at="2026-01-13 13:41:41.0722"></member->
<member- id="4" email="matiascarulli@gmail.com" first-name="Matias" last-name="Carulli" password="$argon2id$v=19$m=65536,t=3,p=4$U2FsdGVkc3M$08SY0x7DUPwTV12ZckHdNg" address1="1234 address NW 12th St" city="Miramar" state="Florida" zipcode="33029" country="US" county="Broward" phone="123-456-789" title="Developer" bio="This is my bio" image-path="/db/images/users/member-4/profile.png" notes="no notes" created="2026-03-15 13:41:41.0722" updated-at="2026-03-15 13:41:41.0722"></member->
<member- id="5" email="boulder@example.com" first-name="CU" last-name="Boulder" password="$argon2id$v=19$m=65536,t=3,p=4$CQwOYXNwwsLBP1s/zcZNJg$OM/wwVP5U+QUnAEDKAjk5mpvujpOzpT0XkouDcmHT8E" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="Associate" bio="This is my bio" notes="no notes" created="2026-03-26 01:03:18.803016+00" updated-at="2026-03-26 01:03:18.803016+00"></member->
<member- id="6" email="sarah.mcintyre@example.com" first-name="Sarah" last-name="McIntyre" password="$argon2id$v=19$m=65536,t=3,p=4$U2FsdGVkc3M$08SY0x7DUPwTV12ZckHdNg" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="Designer" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00"></member->
<member- id="7" email="marcus.webb@example.com" first-name="Marcus" last-name="Webb" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="Engineer" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00"></member->
<member- id="8" email="priya.anand@example.com" first-name="Priya" last-name="Anand" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="PM" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00"></member->
<member- id="9" email="jordan.kim@example.com" first-name="Jordan" last-name="Kim" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="Designer" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00"></member->
<app- id="1" name="settings"></app->
<app- id="2" name="people"></app->
<app- id="3" name="calendar"></app->
<app- id="4" name="treasury"></app->
<app- id="5" name="politics"></app->
<app- id="6" name="files"></app->
<app- id="7" name="jobs"></app->
<app- id="8" name="tasks"></app->
<app- id="9" name="chat"></app->
<app- id="10" name="announcements"></app->
<permission- id="1" key="events.add" app-id="3" description="Can add events"></permission->
<permission- id="2" key="events.delete" app-id="3" description="Can delete events"></permission->
<permission- id="3" key="events.edit" app-id="3" description="Can edit events"></permission->
<permission- id="4" key="events.get" app-id="3" description="Can view events"></permission->
<permission- id="5" key="jobs.add" app-id="7" description="Can add jobs"></permission->
<permission- id="6" key="jobs.delete" app-id="7" description="Can delete jobs"></permission->
<permission- id="7" key="jobs.edit" app-id="7" description="Can edit jobs"></permission->
<permission- id="8" key="jobs.get" app-id="7" description="Can view jobs"></permission->
<permission- id="9" key="announcements.add" app-id="10" description="Can add announcements"></permission->
<permission- id="10" key="announcements.delete" app-id="10" description="Can delete announcements"></permission->
<permission- id="11" key="announcements.edit" app-id="10" description="Can edit announcements"></permission->
<permission- id="12" key="announcements.get" app-id="10" description="Can view announcements"></permission->
<permission- id="13" key="role_apps.edit" app-id="1" description="Can edit role apps"></permission->
<permission- id="14" key="roles.create" app-id="1" description="Can create roles"></permission->
<permission- id="15" key="roles.delete" app-id="1" description="Can delete roles"></permission->
<permission- id="16" key="role_notifications.edit" app-id="1" description="Can edit role notifications"></permission->
<permission- id="17" key="chats.create" app-id="9" description="Can create chats"></permission->
<permission- id="18" key="chats.edit" app-id="9" description="Can edit chats"></permission->
<permission- id="19" key="chats.delete" app-id="9" description="Can delete chats"></permission->
<permission- id="20" key="chats_message.send" app-id="9" description="Can send messages"></permission->
<permission- id="21" key="chats_message.edit" app-id="9" description="Can edit messages"></permission->
<permission- id="22" key="chats_message.delete" app-id="9" description="Can delete messages"></permission->
<member-app- id="1" member-id="1" app-id="1"></member-app->
<member-app- id="2" member-id="4" app-id="1"></member-app->
<member-app- id="3" member-id="6" app-id="1"></member-app->
<member-app- id="4" member-id="1" app-id="2"></member-app->
<member-app- id="5" member-id="4" app-id="2"></member-app->
<member-app- id="6" member-id="6" app-id="2"></member-app->
<member-app- id="7" member-id="1" app-id="3"></member-app->
<member-app- id="8" member-id="4" app-id="3"></member-app->
<member-app- id="9" member-id="6" app-id="3"></member-app->
<member-app- id="10" member-id="1" app-id="4"></member-app->
<member-app- id="11" member-id="4" app-id="4"></member-app->
<member-app- id="12" member-id="6" app-id="4"></member-app->
<member-app- id="13" member-id="1" app-id="5"></member-app->
<member-app- id="14" member-id="4" app-id="5"></member-app->
<member-app- id="15" member-id="6" app-id="5"></member-app->
<member-app- id="16" member-id="1" app-id="6"></member-app->
<member-app- id="17" member-id="4" app-id="6"></member-app->
<member-app- id="18" member-id="6" app-id="6"></member-app->
<member-app- id="19" member-id="1" app-id="7"></member-app->
<member-app- id="20" member-id="4" app-id="7"></member-app->
<member-app- id="21" member-id="6" app-id="7"></member-app->
<member-app- id="22" member-id="1" app-id="8"></member-app->
<member-app- id="23" member-id="4" app-id="8"></member-app->
<member-app- id="24" member-id="6" app-id="8"></member-app->
<member-app- id="25" member-id="1" app-id="9"></member-app->
<member-app- id="26" member-id="4" app-id="10"></member-app->
<member-app- id="27" member-id="6" app-id="9"></member-app->
<!-- network: comalyr -->
<!-- network 2: Hyperia -->
<network- id="2" name="Hyperia" logo="hyperia.svg" abbreviation="hyperia" stripe-account-id="acct_1S4w0GHZemeF9CKR" created="2026-01-10 09:58:01.0074"></network->
<join-code- id="2" code="hyperia" network-id="2"></join-code->
<member-network- id="4" member-id="1" network-id="2" created="2026-01-13 13:28:35.0701"></member-network->
<member-network- id="5" member-id="4" network-id="2" created="2026-03-15 13:28:35.0701"></member-network->
<member-network- id="6" member-id="6" network-id="2" created="2026-02-01 09:00:00+00"></member-network->
<member-network- id="7" member-id="7" network-id="2" created="2026-02-01 09:00:00+00"></member-network->
<member-network- id="8" member-id="8" network-id="2" created="2026-02-01 09:00:00+00"></member-network->
<member-network- id="9" member-id="9" network-id="2" created="2026-02-01 09:00:00+00"></member-network->
<role- id="3" network-id="2" name="admin" is-default="false"></role->
<role- id="4" network-id="2" name="member" is-default="true"></role->
<role-app- id="11" role-id="3" app-id="1" network-id="2"></role-app->
<role-app- id="12" role-id="3" app-id="2" network-id="2"></role-app->
<role-app- id="13" role-id="3" app-id="3" network-id="2"></role-app->
<role-app- id="14" role-id="3" app-id="4" network-id="2"></role-app->
<role-app- id="15" role-id="3" app-id="5" network-id="2"></role-app->
<role-app- id="16" role-id="3" app-id="6" network-id="2"></role-app->
<role-app- id="17" role-id="3" app-id="7" network-id="2"></role-app->
<role-app- id="18" role-id="3" app-id="8" network-id="2"></role-app->
<role-app- id="19" role-id="3" app-id="9" network-id="2"></role-app->
<role-app- id="20" role-id="3" app-id="10" network-id="2"></role-app->
<role-permission- id="29" role-id="3" permission-key="events.get" network-id="2"></role-permission->
<role-permission- id="30" role-id="3" permission-key="jobs.get" network-id="2"></role-permission->
<role-permission- id="31" role-id="3" permission-key="announcements.get" network-id="2"></role-permission->
<role-permission- id="32" role-id="3" permission-key="events.add" network-id="2"></role-permission->
<role-permission- id="33" role-id="3" permission-key="events.delete" network-id="2"></role-permission->
<role-permission- id="34" role-id="3" permission-key="events.edit" network-id="2"></role-permission->
<role-permission- id="35" role-id="3" permission-key="jobs.add" network-id="2"></role-permission->
<role-permission- id="36" role-id="3" permission-key="jobs.delete" network-id="2"></role-permission->
<role-permission- id="37" role-id="3" permission-key="jobs.edit" network-id="2"></role-permission->
<role-permission- id="38" role-id="3" permission-key="announcements.add" network-id="2"></role-permission->
<role-permission- id="39" role-id="3" permission-key="announcements.delete" network-id="2"></role-permission->
<role-permission- id="40" role-id="3" permission-key="announcements.edit" network-id="2"></role-permission->
<role-permission- id="41" role-id="3" permission-key="role_apps.edit" network-id="2"></role-permission->
<role-permission- id="42" role-id="3" permission-key="roles.create" network-id="2"></role-permission->
<role-permission- id="43" role-id="3" permission-key="roles.delete" network-id="2"></role-permission->
<role-permission- id="44" role-id="3" permission-key="chats.create" network-id="2"></role-permission->
<role-permission- id="45" role-id="3" permission-key="chats.edit" network-id="2"></role-permission->
<role-permission- id="46" role-id="3" permission-key="chats.delete" network-id="2"></role-permission->
<role-permission- id="47" role-id="3" permission-key="chats_message.send" network-id="2"></role-permission->
<role-permission- id="48" role-id="3" permission-key="chats_message.edit" network-id="2"></role-permission->
<role-permission- id="49" role-id="3" permission-key="chats_message.delete" network-id="2"></role-permission->
<role-permission- id="50" role-id="3" permission-key="role_notifications.edit" network-id="2"></role-permission->
<role-permission- id="51" role-id="4" permission-key="events.get" network-id="2"></role-permission->
<role-permission- id="52" role-id="4" permission-key="jobs.get" network-id="2"></role-permission->
<role-permission- id="53" role-id="4" permission-key="announcements.get" network-id="2"></role-permission->
<role-permission- id="54" role-id="4" permission-key="events.add" network-id="2"></role-permission->
<role-permission- id="55" role-id="4" permission-key="chats.create" network-id="2"></role-permission->
<role-permission- id="56" role-id="4" permission-key="chats_message.send" network-id="2"></role-permission->
<member-role- id="4" member-id="1" role-id="3" granted-by="1" network-id="2"></member-role->
<member-role- id="5" member-id="4" role-id="3" granted-by="1" network-id="2"></member-role->
<member-role- id="6" member-id="6" role-id="3" granted-by="1" network-id="2"></member-role->
<member-role- id="7" member-id="6" role-id="4" granted-by="1" network-id="2"></member-role->
<member-role- id="8" member-id="7" role-id="4" granted-by="1" network-id="2"></member-role->
<member-role- id="9" member-id="8" role-id="4" granted-by="1" network-id="2"></member-role->
<member-role- id="10" member-id="9" role-id="4" granted-by="1" network-id="2"></member-role->
<calendar- schema="events" id="1" network-id="2" owner-id="1" name="Main Calendar" description="The main calendar for the network" color="#9E1C29"></calendar->
<calendar- schema="events" id="2" network-id="2" owner-id="1" name="Sub-Calendar" description="Sub-calendar for the network" color="#3D6FAD"></calendar->
<calendar- schema="events" id="3" network-id="2" owner-id="1" name="Sub-Calendar 2" description="Another sub-calendar for the network" color="#2A8636"></calendar->
<event-recurrence- schema="events" id="1" frequency="weekly" interval="1" days-of-week="{2}" network-id="2"></event-recurrence->
<event-recurrence- schema="events" id="2" frequency="weekly" interval="2" days-of-week="{3}" count="10" network-id="2"></event-recurrence->
<event-recurrence- schema="events" id="3" frequency="weekly" interval="1" days-of-week="{1,3,5}" network-id="2"></event-recurrence->
<event- schema="events" id="1" network-id="2" creator-id="1" title="Client meeting" description="Meeting with big client for app deployment" time-start="2026-04-01T17:30:00.000Z" time-end="2026-04-01T19:00:00.000Z" location="Virtual" all-day="false" recurrence-id="2"></event->
<event- schema="events" id="2" network-id="2" creator-id="1" title="Networking Event" description="Networking event for young professionals" time-start="2026-04-04T04:00:00.000Z" time-end="2026-04-06T04:00:00.000Z" location="GB Center" all-day="true"></event->
<event- schema="events" id="3" network-id="2" creator-id="1" title="App deployment party" description="Come celebrate the app deployment!" time-start="2026-04-05T16:00:00.000Z" time-end="2026-04-05T21:30:00.000Z" location="Captured Sun HQ" all-day="false"></event->
<event- schema="events" id="4" network-id="2" creator-id="4" title="Work on frontend changes" description="Reminder for work #1" time-start="2026-04-02T15:00:00.000Z" time-end="2026-04-02T19:00:00.000Z" all-day="false"></event->
<event- schema="events" id="5" network-id="2" creator-id="4" title="Day off" description="I dont have to work today" time-start="2026-04-03T04:00:00.000Z" time-end="2026-04-03T04:00:00.000Z" all-day="true"></event->
<event- schema="events" id="6" network-id="2" creator-id="4" title="Scrum Meeting - Main Team" description="Agile Week 32" time-start="2026-03-31T03:00:00.000Z" time-end="2026-03-31T06:15:00.000Z" location="Virtual" all-day="false" recurrence-id="1"></event->
<event- schema="events" id="7" network-id="2" creator-id="1" title="Meeting with John Smiith" description="Lorem ipsum elorum" time-start="2026-03-31T18:30:00.000Z" time-end="2026-03-31T19:30:00.000Z" location="Virtual" all-day="false"></event->
<event- schema="events" id="8" network-id="2" creator-id="1" title="Meeting with Jane Doe" description="Lorem ipsum elorum" time-start="2026-03-31T19:30:00.000Z" time-end="2026-03-31T19:45:00.000Z" location="Virtual" all-day="false"></event->
<event- schema="events" id="9" network-id="2" creator-id="1" title="Meeting with president" description="Lorem ipsum elorum" time-start="2026-03-31T19:45:00.000Z" time-end="2026-03-31T21:15:00.000Z" location="Virtual" all-day="false"></event->
<event- schema="events" id="10" network-id="2" creator-id="1" title="Meeting with investors" description="Lorem ipsum elorum" time-start="2026-03-31T21:30:00.000Z" time-end="2026-03-31T23:00:00.000Z" location="Virtual" all-day="false"></event->
<event- schema="events" id="11" network-id="2" creator-id="1" title="Review Github changes" description="Review pushes from members to ensure all is well" time-start="2026-04-03T04:00:00.000Z" time-end="2026-04-03T04:00:00.000Z" all-day="true" recurrence-id="3"></event->
<event-calendar- schema="events" id="1" event-id="1" calendar-id="1" network-id="2"></event-calendar->
<event-calendar- schema="events" id="2" event-id="2" calendar-id="2" network-id="2"></event-calendar->
<event-calendar- schema="events" id="3" event-id="3" calendar-id="1" network-id="2"></event-calendar->
<event-calendar- schema="events" id="4" event-id="3" calendar-id="3" network-id="2"></event-calendar->
<event-calendar- schema="events" id="5" event-id="4" calendar-id="2" network-id="2"></event-calendar->
<event-calendar- schema="events" id="6" event-id="5" calendar-id="2" network-id="2"></event-calendar->
<event-calendar- schema="events" id="7" event-id="6" calendar-id="1" network-id="2"></event-calendar->
<event-calendar- schema="events" id="8" event-id="6" calendar-id="2" network-id="2"></event-calendar->
<event-calendar- schema="events" id="9" event-id="7" calendar-id="3" network-id="2"></event-calendar->
<event-calendar- schema="events" id="10" event-id="8" calendar-id="2" network-id="2"></event-calendar->
<event-calendar- schema="events" id="11" event-id="8" calendar-id="3" network-id="2"></event-calendar->
<event-calendar- schema="events" id="12" event-id="9" calendar-id="3" network-id="2"></event-calendar->
<event-calendar- schema="events" id="13" event-id="10" calendar-id="3" network-id="2"></event-calendar->
<event-calendar- schema="events" id="14" event-id="11" calendar-id="3" network-id="2"></event-calendar->
<job- id="1" network-id="2" creator-id="1" title="Senior Frontend Engineer" company="Acme Corp" location="San Francisco, CA" employment-type="full-time" experience-level="senior" department="Engineering" salary-number="165000" salary-period="year" applicants="47" skills="React,TypeScript,CSS,GraphQL,Node.js" description="We&#x27;re looking for a Senior Frontend Engineer to join our growing product team. You&#x27;ll work closely with designers, backend engineers, and product managers to build fast, accessible, and beautiful web experiences.&#10;&#10;You&#x27;ll have ownership over large features and be expected to make architectural decisions. We ship every week and care deeply about code quality and UX." created="2026-04-29 10:00:00" updated-at="2026-04-29 10:00:00"></job->
<job- id="2" network-id="2" creator-id="1" title="Product Designer" company="Blue River" location="New York, NY" employment-type="full-time" experience-level="mid" department="Design" salary-number="120000" salary-period="year" applicants="112" skills="Figma,Design Systems,Prototyping,User Research" description="Blue River is hiring a Product Designer to lead design across our core consumer product. You&#x27;ll partner with PMs and engineers to take features from zero to one, establish design patterns, and advocate for users in every decision.&#10;&#10;We&#x27;re a small, fast-moving team and designers have a huge impact here." created="2026-04-26 09:00:00" updated-at="2026-04-26 09:00:00"></job->
<job- id="3" network-id="2" creator-id="1" title="Backend Engineer" company="Orbit Systems" location="Remote" employment-type="contract" experience-level="mid" department="Platform" salary-number="95" salary-period="hour" applicants="29" skills="Go,PostgreSQL,Kubernetes,gRPC,Redis" description="6-month contract (potential to extend) for a backend engineer to help scale our platform infrastructure. You&#x27;ll be responsible for building and maintaining APIs used by millions of users, optimizing database performance, and improving system reliability." created="2026-04-30 14:00:00" updated-at="2026-04-30 14:00:00"></job->
<job- id="4" network-id="2" creator-id="1" title="Marketing Manager" company="Groundwork" location="Austin, TX" employment-type="full-time" experience-level="mid" department="Marketing" salary-number="98000" salary-period="year" applicants="88" skills="SEO,Content Strategy,Analytics,Paid Acquisition,Email Marketing" description="We need a Marketing Manager to own our top-of-funnel growth. You&#x27;ll manage content, paid channels, and email campaigns — and be the person who keeps the brand consistent across every touchpoint. You&#x27;ll report directly to our Head of Growth." created="2026-04-21 11:00:00" updated-at="2026-04-21 11:00:00"></job->
<job- id="5" network-id="2" creator-id="1" title="Data Analyst" company="Compass Data" location="Chicago, IL" employment-type="full-time" experience-level="entry" department="Analytics" salary-number="75000" salary-period="year" applicants="203" skills="SQL,Python,Tableau,dbt,Excel" description="A great entry-level opportunity for someone who loves data. You&#x27;ll work with our analytics team to build dashboards, run ad-hoc analysis, and help business teams make data-driven decisions. We&#x27;ll invest in your growth and give you plenty of mentorship." created="2026-04-28 08:30:00" updated-at="2026-04-28 08:30:00"></job->
<job- id="6" network-id="2" creator-id="1" title="iOS Engineer" company="Fieldwork" location="Seattle, WA" employment-type="full-time" experience-level="senior" department="Mobile" salary-number="175000" salary-period="year" applicants="34" skills="Swift,SwiftUI,Combine,CoreData,Xcode" description="Fieldwork is building next-generation tools for field service teams. Our iOS app is the most important surface we have — technicians use it every day on job sites. We need a senior iOS engineer who cares about performance, offline reliability, and a great UX." created="2026-04-24 13:00:00" updated-at="2026-04-24 13:00:00"></job->
<job- id="7" network-id="2" creator-id="1" title="Operations Coordinator" company="Maple &amp; Co" location="Boston, MA" employment-type="part-time" experience-level="entry" department="Operations" salary-number="28" salary-period="hour" applicants="61" skills="Project Management,Excel,Communication,Scheduling" description="Part-time role (20 hrs/week) helping our operations team stay organized. You&#x27;ll coordinate schedules, manage vendor relationships, and help improve internal workflows. Ideal for someone who&#x27;s highly organized and excited to grow into a full-time ops role." created="2026-04-17 10:00:00" updated-at="2026-04-17 10:00:00"></job->
<job- id="8" network-id="2" creator-id="1" title="Machine Learning Intern" company="NeuralPath" location="Remote" employment-type="internship" experience-level="entry" department="AI Research" salary-number="8000" salary-period="month" applicants="394" skills="Python,PyTorch,Linear Algebra,Git" description="Summer internship (12 weeks) on our ML research team. You&#x27;ll work alongside researchers on real problems in NLP and recommendation systems. We expect you to ship something you&#x27;re proud of by the end of the summer. Strong preference for candidates who can start June 2." created="2026-05-01 09:00:00" updated-at="2026-05-01 09:00:00"></job->
<announcement- id="1" network-id="2" creator-id="1" text="This is the first announcement" created="2026-01-13 15:37:00.0000" updated-at="2026-01-13 15:37:00.0000"></announcement->
<announcement- id="2" network-id="2" creator-id="1" text="Here goes another announcement." created="2026-01-13 15:39:00.0000" updated-at="2026-01-13 15:39:00.0000"></announcement->
<announcement- id="3" network-id="2" creator-id="4" text="My first announcement!" created="2026-01-14 15:41:00.0000" updated-at="2026-01-14 15:41:00.0000"></announcement->
<announcement- id="4" network-id="2" creator-id="1" text="Testing announcement." created="2026-02-17 12:30:00.0000" updated-at="2026-02-17 12:30:00.0000"></announcement->
<announcement- id="5" network-id="2" creator-id="4" text="Quick fire!" created="2026-03-23 15:56:00.0000" updated-at="2026-03-23 15:56:00.0000"></announcement->
<announcement- id="6" network-id="2" creator-id="4" text="Quick fire!" created="2026-03-23 15:56:30.0000" updated-at="2026-03-23 15:56:30.0000"></announcement->
<announcement- id="7" network-id="2" creator-id="4" text="Quick fire!" created="2026-03-23 15:57:00.0000" updated-at="2026-03-23 15:57:00.0000"></announcement->
<announcement- id="8" network-id="2" creator-id="1" text="Trying another user." created="2026-02-05 15:56:30.0000" updated-at="2026-02-05 15:56:30.0000"></announcement->
<announcement- id="9" network-id="2" creator-id="1" text="One last announcement." created="2026-04-01 15:57:00.0000" updated-at="2026-01-13 15:57:00.0000"></announcement->
<chat- schema="chats" id="1" network-id="2" creator-id="4" type="dm" created="2026-04-01 09:00:00+00" updated-at="2026-05-01 11:42:00+00"></chat->
<chat- schema="chats" id="2" network-id="2" creator-id="4" type="dm" created="2026-04-01 09:00:00+00" updated-at="2026-05-01 10:30:00+00"></chat->
<chat- schema="chats" id="3" network-id="2" creator-id="4" type="dm" created="2026-04-01 09:00:00+00" updated-at="2026-04-30 18:00:00+00"></chat->
<chat- schema="chats" id="4" network-id="2" creator-id="4" type="group" name="Product Team" created="2026-04-01 09:00:00+00" updated-at="2026-05-01 11:51:00+00"></chat->
<chat- schema="chats" id="5" network-id="2" creator-id="4" type="group" name="Design Review" created="2026-04-01 09:00:00+00" updated-at="2026-04-30 10:00:00+00"></chat->
<chat- schema="chats" id="6" network-id="2" creator-id="4" type="channel" name="general" created="2026-04-01 09:00:00+00" updated-at="2026-05-01 11:55:00+00"></chat->
<chat- schema="chats" id="7" network-id="2" creator-id="4" type="channel" name="engineering" created="2026-04-01 09:00:00+00" updated-at="2026-05-01 09:00:00+00"></chat->
<chat- schema="chats" id="8" network-id="2" creator-id="8" type="announcement" name="Announcements" created="2026-04-01 09:00:00+00" updated-at="2026-05-01 00:00:00+00"></chat->
<chat-member- schema="chats" id="1" chat-id="1" member-id="4" last-read-at="2026-05-01 07:12:00+00" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="2" chat-id="1" member-id="6" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="3" chat-id="2" member-id="4" last-read-at="2026-05-01 10:30:00+00" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="4" chat-id="2" member-id="7" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="5" chat-id="3" member-id="4" last-read-at="2026-04-30 18:00:00+00" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="6" chat-id="3" member-id="8" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="7" chat-id="4" member-id="4" last-read-at="2026-05-01 04:30:00+00" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="8" chat-id="4" member-id="6" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="9" chat-id="4" member-id="7" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="10" chat-id="4" member-id="8" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="11" chat-id="5" member-id="4" last-read-at="2026-04-30 10:00:00+00" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="12" chat-id="5" member-id="6" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="13" chat-id="5" member-id="9" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="14" chat-id="6" member-id="1" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="15" chat-id="6" member-id="4" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="16" chat-id="6" member-id="6" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="17" chat-id="6" member-id="7" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="18" chat-id="6" member-id="8" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="19" chat-id="6" member-id="9" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="20" chat-id="7" member-id="1" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="21" chat-id="7" member-id="4" last-read-at="2026-05-01 09:00:00+00" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="22" chat-id="7" member-id="7" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="23" chat-id="8" member-id="1" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="24" chat-id="8" member-id="4" last-read-at="2026-04-29 12:00:00+00" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="25" chat-id="8" member-id="6" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="26" chat-id="8" member-id="7" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="27" chat-id="8" member-id="8" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<chat-member- schema="chats" id="28" chat-id="8" member-id="9" joined-at="2026-04-01 09:00:00+00" network-id="2"></chat-member->
<message- schema="chats" id="1" chat-id="1" sender-id="4" text="Hey Sarah, did you see the new design mockups?" sent-at="2026-04-30 12:00:00+00" updated-at="2026-04-30 12:00:00+00" network-id="2"></message->
<message- schema="chats" id="2" chat-id="1" sender-id="6" text="Just looked — they&#x27;re really clean. I love the new sidebar." sent-at="2026-04-30 12:30:00+00" updated-at="2026-04-30 12:30:00+00" network-id="2"></message->
<message- schema="chats" id="3" chat-id="1" sender-id="4" text="Agreed. Alex did a great job." sent-at="2026-04-30 12:36:00+00" updated-at="2026-04-30 12:36:00+00" network-id="2"></message->
<message- schema="chats" id="4" chat-id="1" sender-id="6" text="Are we going to ship this week or wait for the backend?" sent-at="2026-05-01 07:00:00+00" updated-at="2026-05-01 07:00:00+00" network-id="2"></message->
<message- schema="chats" id="5" chat-id="1" sender-id="4" text="Let&#x27;s aim for Thursday. I&#x27;ll sync with Marcus." sent-at="2026-05-01 07:12:00+00" updated-at="2026-05-01 07:12:00+00" network-id="2"></message->
<message- schema="chats" id="6" chat-id="1" sender-id="6" text="Sounds good 👍" sent-at="2026-05-01 07:18:00+00" updated-at="2026-05-01 07:18:00+00" network-id="2"></message->
<message- schema="chats" id="7" chat-id="1" sender-id="6" text="Can you review the PR when you get a chance?" sent-at="2026-05-01 11:42:00+00" updated-at="2026-05-01 11:42:00+00" network-id="2"></message->
<message- schema="chats" id="8" chat-id="2" sender-id="7" text="Hey, the API endpoint is returning 500s on staging." sent-at="2026-05-01 09:00:00+00" updated-at="2026-05-01 09:00:00+00" network-id="2"></message->
<message- schema="chats" id="9" chat-id="2" sender-id="4" text="Oh no — is it the auth middleware again?" sent-at="2026-05-01 09:06:00+00" updated-at="2026-05-01 09:06:00+00" network-id="2"></message->
<message- schema="chats" id="10" chat-id="2" sender-id="7" text="Yep. Same issue as last week." sent-at="2026-05-01 09:12:00+00" updated-at="2026-05-01 09:12:00+00" network-id="2"></message->
<message- schema="chats" id="11" chat-id="2" sender-id="4" text="I&#x27;ll patch it now. Give me 20 mins." sent-at="2026-05-01 09:15:00+00" updated-at="2026-05-01 09:15:00+00" network-id="2"></message->
<message- schema="chats" id="12" chat-id="2" sender-id="7" text="Thanks, no rush." sent-at="2026-05-01 09:18:00+00" updated-at="2026-05-01 09:18:00+00" network-id="2"></message->
<message- schema="chats" id="13" chat-id="2" sender-id="4" text="Fixed. Can you redeploy and check?" sent-at="2026-05-01 09:48:00+00" updated-at="2026-05-01 09:48:00+00" network-id="2"></message->
<message- schema="chats" id="14" chat-id="2" sender-id="7" text="All green 🎉 Thanks!" sent-at="2026-05-01 09:54:00+00" updated-at="2026-05-01 09:54:00+00" network-id="2"></message->
<message- schema="chats" id="15" chat-id="2" sender-id="4" text="I&#x27;ll send over the specs by EOD" sent-at="2026-05-01 10:30:00+00" updated-at="2026-05-01 10:30:00+00" network-id="2"></message->
<message- schema="chats" id="16" chat-id="3" sender-id="8" text="Quick question — what&#x27;s the launch date for v2?" sent-at="2026-04-30 16:00:00+00" updated-at="2026-04-30 16:00:00+00" network-id="2"></message->
<message- schema="chats" id="17" chat-id="3" sender-id="4" text="Still TBD, but we&#x27;re targeting end of May." sent-at="2026-04-30 16:12:00+00" updated-at="2026-04-30 16:12:00+00" network-id="2"></message->
<message- schema="chats" id="18" chat-id="3" sender-id="8" text="Got it. I&#x27;ll update the roadmap doc." sent-at="2026-04-30 16:30:00+00" updated-at="2026-04-30 16:30:00+00" network-id="2"></message->
<message- schema="chats" id="19" chat-id="3" sender-id="4" text="Perfect, thanks Priya." sent-at="2026-04-30 16:36:00+00" updated-at="2026-04-30 16:36:00+00" network-id="2"></message->
<message- schema="chats" id="20" chat-id="3" sender-id="8" text="See you at the standup!" sent-at="2026-04-30 18:00:00+00" updated-at="2026-04-30 18:00:00+00" network-id="2"></message->
<message- schema="chats" id="21" chat-id="4" sender-id="7" text="Morning everyone! API docs are updated." sent-at="2026-05-01 04:00:00+00" updated-at="2026-05-01 04:00:00+00" network-id="2"></message->
<message- schema="chats" id="22" chat-id="4" sender-id="8" text="Nice work Marcus 🙌" sent-at="2026-05-01 04:06:00+00" updated-at="2026-05-01 04:06:00+00" network-id="2"></message->
<message- schema="chats" id="23" chat-id="4" sender-id="4" text="I&#x27;ll start on the integration tests today." sent-at="2026-05-01 04:12:00+00" updated-at="2026-05-01 04:12:00+00" network-id="2"></message->
<message- schema="chats" id="24" chat-id="4" sender-id="6" text="Great. I&#x27;m finishing up the onboarding screens." sent-at="2026-05-01 04:30:00+00" updated-at="2026-05-01 04:30:00+00" network-id="2"></message->
<message- schema="chats" id="25" chat-id="4" sender-id="8" text="Can we do a quick sync at 2pm?" sent-at="2026-05-01 10:00:00+00" updated-at="2026-05-01 10:00:00+00" network-id="2"></message->
<message- schema="chats" id="26" chat-id="4" sender-id="4" text="Works for me." sent-at="2026-05-01 10:03:00+00" updated-at="2026-05-01 10:03:00+00" network-id="2"></message->
<message- schema="chats" id="27" chat-id="4" sender-id="7" text="Same" sent-at="2026-05-01 10:06:00+00" updated-at="2026-05-01 10:06:00+00" network-id="2"></message->
<message- schema="chats" id="28" chat-id="4" sender-id="6" text="I&#x27;ll send the invite." sent-at="2026-05-01 10:09:00+00" updated-at="2026-05-01 10:09:00+00" network-id="2"></message->
<message- schema="chats" id="29" chat-id="4" sender-id="6" text="I&#x27;ve updated the Figma file with the new flows" sent-at="2026-05-01 11:51:00+00" updated-at="2026-05-01 11:51:00+00" network-id="2"></message->
<message- schema="chats" id="30" chat-id="5" sender-id="9" text="Hey, sharing the first round of designs for the settings page." sent-at="2026-04-30 06:00:00+00" updated-at="2026-04-30 06:00:00+00" network-id="2"></message->
<message- schema="chats" id="31" chat-id="5" sender-id="6" text="These look great! Love the card layout." sent-at="2026-04-30 06:30:00+00" updated-at="2026-04-30 06:30:00+00" network-id="2"></message->
<message- schema="chats" id="32" chat-id="5" sender-id="4" text="Agreed. One thought — the spacing on the form feels a bit tight." sent-at="2026-04-30 07:00:00+00" updated-at="2026-04-30 07:00:00+00" network-id="2"></message->
<message- schema="chats" id="33" chat-id="5" sender-id="9" text="Good call. I&#x27;ll loosen it up." sent-at="2026-04-30 07:12:00+00" updated-at="2026-04-30 07:12:00+00" network-id="2"></message->
<message- schema="chats" id="34" chat-id="5" sender-id="6" text="Also maybe we increase the font size slightly?" sent-at="2026-04-30 08:00:00+00" updated-at="2026-04-30 08:00:00+00" network-id="2"></message->
<message- schema="chats" id="35" chat-id="5" sender-id="9" text="The contrast on mobile looks off — can we bump it?" sent-at="2026-04-30 10:00:00+00" updated-at="2026-04-30 10:00:00+00" network-id="2"></message->
<message- schema="chats" id="36" chat-id="6" sender-id="8" text="Good morning team! Reminder: all-hands is Thursday at 10am." sent-at="2026-05-01 03:00:00+00" updated-at="2026-05-01 03:00:00+00" network-id="2"></message->
<message- schema="chats" id="37" chat-id="6" sender-id="6" text="Thanks for the reminder!" sent-at="2026-05-01 03:06:00+00" updated-at="2026-05-01 03:06:00+00" network-id="2"></message->
<message- schema="chats" id="38" chat-id="6" sender-id="9" text="Will there be a recording for those in other time zones?" sent-at="2026-05-01 03:18:00+00" updated-at="2026-05-01 03:18:00+00" network-id="2"></message->
<message- schema="chats" id="39" chat-id="6" sender-id="8" text="Yes — I&#x27;ll post the link in #announcements after." sent-at="2026-05-01 03:24:00+00" updated-at="2026-05-01 03:24:00+00" network-id="2"></message->
<message- schema="chats" id="40" chat-id="6" sender-id="4" text="Thanks Priya 🙏" sent-at="2026-05-01 03:30:00+00" updated-at="2026-05-01 03:30:00+00" network-id="2"></message->
<message- schema="chats" id="41" chat-id="6" sender-id="7" text="Staging is back up btw, had a brief outage this morning." sent-at="2026-05-01 08:00:00+00" updated-at="2026-05-01 08:00:00+00" network-id="2"></message->
<message- schema="chats" id="42" chat-id="6" sender-id="6" text="Oh I didn&#x27;t even notice, nice quick fix!" sent-at="2026-05-01 08:12:00+00" updated-at="2026-05-01 08:12:00+00" network-id="2"></message->
<message- schema="chats" id="43" chat-id="6" sender-id="7" text="Just pushed the hotfix to production" sent-at="2026-05-01 11:55:00+00" updated-at="2026-05-01 11:55:00+00" network-id="2"></message->
<message- schema="chats" id="44" chat-id="7" sender-id="7" text="Heads up: I&#x27;m updating the CI pipeline today. Builds might be slow for a bit." sent-at="2026-05-01 07:00:00+00" updated-at="2026-05-01 07:00:00+00" network-id="2"></message->
<message- schema="chats" id="45" chat-id="7" sender-id="4" text="Noted, thanks for the warning." sent-at="2026-05-01 07:06:00+00" updated-at="2026-05-01 07:06:00+00" network-id="2"></message->
<message- schema="chats" id="46" chat-id="7" sender-id="7" text="Back to normal now." sent-at="2026-05-01 08:00:00+00" updated-at="2026-05-01 08:00:00+00" network-id="2"></message->
<message- schema="chats" id="47" chat-id="7" sender-id="4" text="PR is up: #247 — adds rate limiting to the auth routes" sent-at="2026-05-01 09:00:00+00" updated-at="2026-05-01 09:00:00+00" network-id="2"></message->
<message- schema="chats" id="48" chat-id="8" sender-id="8" text="Welcome to the team, Jordan! 🎉 Jordan joins us as a Product Designer." sent-at="2026-04-28 12:00:00+00" updated-at="2026-04-28 12:00:00+00" network-id="2"></message->
<message- schema="chats" id="49" chat-id="8" sender-id="8" text="Reminder: expense reports for March are due this Friday." sent-at="2026-04-29 12:00:00+00" updated-at="2026-04-29 12:00:00+00" network-id="2"></message->
<message- schema="chats" id="50" chat-id="8" sender-id="8" text="Q2 planning kick-off is next Monday at 9am. Please come prepared with your team&#x27;s priorities." sent-at="2026-05-01 00:00:00+00" updated-at="2026-05-01 00:00:00+00" network-id="2"></message->
</forms->

134
priv/networks/cs/index.html Normal file
View File

@@ -0,0 +1,134 @@
<forms->
<!-- global -->
<member- id="1" email="samrussell99@pm.me" first-name="Sam" last-name="Russell" password="$argon2id$v=19$m=65536,t=3,p=4$n/8BaBisEnBaQNbkxzs1VA$dvvnupWNtB5w5qTBgEciDsNA6rOgXaEypcEK1A0ndLM" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="CEO" bio="This is my bio" notes="no notes" created="2026-01-15 09:58:01.0072" updated-at="2026-01-15 09:58:01.0072"></member->
<member- id="2" email="freddyjkrueger@gmail.com" first-name="Freddy" last-name="Krueger" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="Villain" bio="This is my bio" notes="no notes" created="2026-01-13 13:38:46.0810" updated-at="2026-01-13 13:38:46.0810"></member->
<member- id="3" email="harmysmarmy@gmail.com" first-name="Harmy" last-name="Smarmy" password="$argon2id$v=19$m=65536,t=3,p=4$FAhGtCtqNAQ19tBYD73wXQ$0AM/khyBFFuX2mv0ieqtGfsXRgtEldWKFwyeV3BA3Xk" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="Associate" bio="This is my bio" notes="no notes" created="2026-01-13 13:41:41.0722" updated-at="2026-01-13 13:41:41.0722"></member->
<member- id="4" email="matiascarulli@gmail.com" first-name="Matias" last-name="Carulli" password="$argon2id$v=19$m=65536,t=3,p=4$U2FsdGVkc3M$08SY0x7DUPwTV12ZckHdNg" address1="1234 address NW 12th St" city="Miramar" state="Florida" zipcode="33029" country="US" county="Broward" phone="123-456-789" title="Developer" bio="This is my bio" image-path="/db/images/users/member-4/profile.png" notes="no notes" created="2026-03-15 13:41:41.0722" updated-at="2026-03-15 13:41:41.0722"></member->
<member- id="5" email="boulder@example.com" first-name="CU" last-name="Boulder" password="$argon2id$v=19$m=65536,t=3,p=4$CQwOYXNwwsLBP1s/zcZNJg$OM/wwVP5U+QUnAEDKAjk5mpvujpOzpT0XkouDcmHT8E" address1="1234 address NW 12th St" city="Austin" state="Texas" zipcode="12345" country="US" county="Austin County" phone="123-456-789" title="Associate" bio="This is my bio" notes="no notes" created="2026-03-26 01:03:18.803016+00" updated-at="2026-03-26 01:03:18.803016+00"></member->
<member- id="6" email="sarah.mcintyre@example.com" first-name="Sarah" last-name="McIntyre" password="$argon2id$v=19$m=65536,t=3,p=4$U2FsdGVkc3M$08SY0x7DUPwTV12ZckHdNg" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="Designer" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00"></member->
<member- id="7" email="marcus.webb@example.com" first-name="Marcus" last-name="Webb" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="Engineer" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00"></member->
<member- id="8" email="priya.anand@example.com" first-name="Priya" last-name="Anand" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="PM" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00"></member->
<member- id="9" email="jordan.kim@example.com" first-name="Jordan" last-name="Kim" password="$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM" address1="1234 Address St" city="Austin" state="Texas" zipcode="12345" country="US" county="Travis" phone="123-456-789" title="Designer" bio="This is my bio" created="2026-02-01 09:00:00+00" updated-at="2026-02-01 09:00:00+00"></member->
<app- id="1" name="settings"></app->
<app- id="2" name="people"></app->
<app- id="3" name="calendar"></app->
<app- id="4" name="treasury"></app->
<app- id="5" name="politics"></app->
<app- id="6" name="files"></app->
<app- id="7" name="jobs"></app->
<app- id="8" name="tasks"></app->
<app- id="9" name="chat"></app->
<app- id="10" name="announcements"></app->
<permission- id="1" key="events.add" app-id="3" description="Can add events"></permission->
<permission- id="2" key="events.delete" app-id="3" description="Can delete events"></permission->
<permission- id="3" key="events.edit" app-id="3" description="Can edit events"></permission->
<permission- id="4" key="events.get" app-id="3" description="Can view events"></permission->
<permission- id="5" key="jobs.add" app-id="7" description="Can add jobs"></permission->
<permission- id="6" key="jobs.delete" app-id="7" description="Can delete jobs"></permission->
<permission- id="7" key="jobs.edit" app-id="7" description="Can edit jobs"></permission->
<permission- id="8" key="jobs.get" app-id="7" description="Can view jobs"></permission->
<permission- id="9" key="announcements.add" app-id="10" description="Can add announcements"></permission->
<permission- id="10" key="announcements.delete" app-id="10" description="Can delete announcements"></permission->
<permission- id="11" key="announcements.edit" app-id="10" description="Can edit announcements"></permission->
<permission- id="12" key="announcements.get" app-id="10" description="Can view announcements"></permission->
<permission- id="13" key="role_apps.edit" app-id="1" description="Can edit role apps"></permission->
<permission- id="14" key="roles.create" app-id="1" description="Can create roles"></permission->
<permission- id="15" key="roles.delete" app-id="1" description="Can delete roles"></permission->
<permission- id="16" key="role_notifications.edit" app-id="1" description="Can edit role notifications"></permission->
<permission- id="17" key="chats.create" app-id="9" description="Can create chats"></permission->
<permission- id="18" key="chats.edit" app-id="9" description="Can edit chats"></permission->
<permission- id="19" key="chats.delete" app-id="9" description="Can delete chats"></permission->
<permission- id="20" key="chats_message.send" app-id="9" description="Can send messages"></permission->
<permission- id="21" key="chats_message.edit" app-id="9" description="Can edit messages"></permission->
<permission- id="22" key="chats_message.delete" app-id="9" description="Can delete messages"></permission->
<member-app- id="1" member-id="1" app-id="1"></member-app->
<member-app- id="2" member-id="4" app-id="1"></member-app->
<member-app- id="3" member-id="6" app-id="1"></member-app->
<member-app- id="4" member-id="1" app-id="2"></member-app->
<member-app- id="5" member-id="4" app-id="2"></member-app->
<member-app- id="6" member-id="6" app-id="2"></member-app->
<member-app- id="7" member-id="1" app-id="3"></member-app->
<member-app- id="8" member-id="4" app-id="3"></member-app->
<member-app- id="9" member-id="6" app-id="3"></member-app->
<member-app- id="10" member-id="1" app-id="4"></member-app->
<member-app- id="11" member-id="4" app-id="4"></member-app->
<member-app- id="12" member-id="6" app-id="4"></member-app->
<member-app- id="13" member-id="1" app-id="5"></member-app->
<member-app- id="14" member-id="4" app-id="5"></member-app->
<member-app- id="15" member-id="6" app-id="5"></member-app->
<member-app- id="16" member-id="1" app-id="6"></member-app->
<member-app- id="17" member-id="4" app-id="6"></member-app->
<member-app- id="18" member-id="6" app-id="6"></member-app->
<member-app- id="19" member-id="1" app-id="7"></member-app->
<member-app- id="20" member-id="4" app-id="7"></member-app->
<member-app- id="21" member-id="6" app-id="7"></member-app->
<member-app- id="22" member-id="1" app-id="8"></member-app->
<member-app- id="23" member-id="4" app-id="8"></member-app->
<member-app- id="24" member-id="6" app-id="8"></member-app->
<member-app- id="25" member-id="1" app-id="9"></member-app->
<member-app- id="26" member-id="4" app-id="10"></member-app->
<member-app- id="27" member-id="6" app-id="9"></member-app->
<!-- network: cs -->
<!-- network 1: Captured Sun -->
<network- id="1" name="Captured Sun" logo="cs.svg" abbreviation="cs" stripe-account-id="acct_1Sn6DwLpyskwAml9" created="2026-01-10 09:58:01.0074"></network->
<network-plan- id="1" network-id="1" stripe-price-id="price_1T3uaxLpyskwAml9p0r0nh2h" name="Patron Membership" price="200.00" description="Members 40+" active="true" created="2026-03-29 22:14:45.414163"></network-plan->
<network-plan- id="2" network-id="1" stripe-price-id="price_1T3uaQLpyskwAml9rZAKBcy0" name="Regular Membership" price="100.00" description="Members 18-40" active="true" created="2026-03-29 22:14:45.414163"></network-plan->
<join-code- id="1" code="cs" network-id="1"></join-code->
<member-network- id="1" member-id="1" network-id="1" created="2025-11-24 00:54:36.0784"></member-network->
<member-network- id="2" member-id="2" network-id="1" created="2026-01-13 13:14:28.0178"></member-network->
<member-network- id="3" member-id="3" network-id="1" created="2026-01-13 13:28:35.0701"></member-network->
<role- id="1" network-id="1" name="admin" is-default="false"></role->
<role- id="2" network-id="1" name="member" is-default="true"></role->
<role-app- id="1" role-id="1" app-id="1" network-id="1"></role-app->
<role-app- id="2" role-id="1" app-id="2" network-id="1"></role-app->
<role-app- id="3" role-id="1" app-id="3" network-id="1"></role-app->
<role-app- id="4" role-id="1" app-id="4" network-id="1"></role-app->
<role-app- id="5" role-id="1" app-id="5" network-id="1"></role-app->
<role-app- id="6" role-id="1" app-id="6" network-id="1"></role-app->
<role-app- id="7" role-id="1" app-id="7" network-id="1"></role-app->
<role-app- id="8" role-id="1" app-id="8" network-id="1"></role-app->
<role-app- id="9" role-id="1" app-id="9" network-id="1"></role-app->
<role-app- id="10" role-id="1" app-id="10" network-id="1"></role-app->
<role-permission- id="1" role-id="1" permission-key="events.get" network-id="1"></role-permission->
<role-permission- id="2" role-id="1" permission-key="jobs.get" network-id="1"></role-permission->
<role-permission- id="3" role-id="1" permission-key="announcements.get" network-id="1"></role-permission->
<role-permission- id="4" role-id="1" permission-key="events.add" network-id="1"></role-permission->
<role-permission- id="5" role-id="1" permission-key="events.delete" network-id="1"></role-permission->
<role-permission- id="6" role-id="1" permission-key="events.edit" network-id="1"></role-permission->
<role-permission- id="7" role-id="1" permission-key="jobs.add" network-id="1"></role-permission->
<role-permission- id="8" role-id="1" permission-key="jobs.delete" network-id="1"></role-permission->
<role-permission- id="9" role-id="1" permission-key="jobs.edit" network-id="1"></role-permission->
<role-permission- id="10" role-id="1" permission-key="announcements.add" network-id="1"></role-permission->
<role-permission- id="11" role-id="1" permission-key="announcements.delete" network-id="1"></role-permission->
<role-permission- id="12" role-id="1" permission-key="announcements.edit" network-id="1"></role-permission->
<role-permission- id="13" role-id="1" permission-key="role_apps.edit" network-id="1"></role-permission->
<role-permission- id="14" role-id="1" permission-key="roles.create" network-id="1"></role-permission->
<role-permission- id="15" role-id="1" permission-key="roles.delete" network-id="1"></role-permission->
<role-permission- id="16" role-id="1" permission-key="chats.create" network-id="1"></role-permission->
<role-permission- id="17" role-id="1" permission-key="chats.edit" network-id="1"></role-permission->
<role-permission- id="18" role-id="1" permission-key="chats.delete" network-id="1"></role-permission->
<role-permission- id="19" role-id="1" permission-key="chats_message.send" network-id="1"></role-permission->
<role-permission- id="20" role-id="1" permission-key="chats_message.edit" network-id="1"></role-permission->
<role-permission- id="21" role-id="1" permission-key="chats_message.delete" network-id="1"></role-permission->
<role-permission- id="22" role-id="1" permission-key="role_notifications.edit" network-id="1"></role-permission->
<role-permission- id="23" role-id="2" permission-key="events.get" network-id="1"></role-permission->
<role-permission- id="24" role-id="2" permission-key="jobs.get" network-id="1"></role-permission->
<role-permission- id="25" role-id="2" permission-key="announcements.get" network-id="1"></role-permission->
<role-permission- id="26" role-id="2" permission-key="events.add" network-id="1"></role-permission->
<role-permission- id="27" role-id="2" permission-key="chats.create" network-id="1"></role-permission->
<role-permission- id="28" role-id="2" permission-key="chats_message.send" network-id="1"></role-permission->
<member-role- id="1" member-id="1" role-id="1" granted-by="1" network-id="1"></member-role->
<member-role- id="2" member-id="2" role-id="2" granted-by="1" network-id="1"></member-role->
<member-role- id="3" member-id="3" role-id="2" granted-by="1" network-id="1"></member-role->
<join-form- schema="org_1" id="1" fname="James" lname="Mitchell" email="james.mitchell@gmail.com" phone="512-555-0101" county="Comal" time="2025-12-16 23:11:31.0011" network-id="1"></join-form->
<join-form- schema="org_1" id="2" fname="Rachel" lname="Torres" email="rachel.torres@yahoo.com" phone="512-555-0102" county="Bexar" time="2025-12-19 19:23:12.0717" network-id="1"></join-form->
<join-form- schema="org_1" id="3" fname="David" lname="Nguyen" email="david.nguyen@gmail.com" phone="830-555-0103" county="Comal" time="2026-01-06 16:55:29.0288" network-id="1"></join-form->
<join-form- schema="org_1" id="4" fname="Emily" lname="Sanders" email="emily.sanders@outlook.com" phone="210-555-0104" county="Hays" time="2026-01-07 17:14:01.0711" network-id="1"></join-form->
<contact-form- schema="org_1" id="1" fname="Marcus" lname="Webb" email="marcus.webb@gmail.com" phone="512-555-0201" county="Comal" message="Interested in volunteering at upcoming events." time="2025-12-29 13:20:28.0157" network-id="1"></contact-form->
<contact-form- schema="org_1" id="2" fname="Sandra" lname="Holloway" email="sandra.holloway@gmail.com" phone="830-555-0202" county="Comal" message="Would love to connect with your organization." time="2025-12-30 22:10:24.0971" network-id="1"></contact-form->
<contact-form- schema="org_1" id="3" fname="Robert" lname="Finley" email="robert.finley@gmail.com" phone="210-555-0203" county="Comal" message="Looking forward to getting more involved locally." time="2026-01-10 21:23:51.0073" network-id="1"></contact-form->
<contact-form- schema="org_1" id="4" fname="Barbara" lname="Crane" email="barbara.crane@outlook.com" phone="512-555-0204" county="Comal" message="Please reach out regarding the next meeting schedule." time="2026-01-10 21:23:54.0841" network-id="1"></contact-form->
</forms->

Binary file not shown.

View File

@@ -0,0 +1,93 @@
Copyright (c) 2010, Igino Marini (mail@iginomarini.com)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

View File

@@ -0,0 +1,29 @@
<svg width="534" height="534" viewBox="0 0 534 534" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="9" height="534" fill="black"/>
<rect x="21" width="9" height="534" fill="black"/>
<rect x="42" width="9" height="534" fill="black"/>
<rect x="63" width="9" height="534" fill="black"/>
<rect x="84" width="9" height="534" fill="black"/>
<rect x="105" width="9" height="534" fill="black"/>
<rect x="126" width="9" height="534" fill="black"/>
<rect x="147" width="9" height="534" fill="black"/>
<rect x="168" width="9" height="534" fill="black"/>
<rect x="189" width="9" height="534" fill="black"/>
<rect x="210" width="9" height="534" fill="black"/>
<rect x="231" width="9" height="534" fill="black"/>
<circle cx="266.5" cy="271.5" r="127.5" fill="#FF0000"/>
<rect x="273" width="9" height="534" fill="black"/>
<rect x="294" width="9" height="534" fill="black"/>
<rect x="315" width="9" height="534" fill="black"/>
<rect x="336" width="9" height="534" fill="black"/>
<rect x="357" width="9" height="534" fill="black"/>
<rect x="378" width="9" height="534" fill="black"/>
<rect x="399" width="9" height="534" fill="black"/>
<rect x="420" width="9" height="534" fill="black"/>
<rect x="441" width="9" height="534" fill="black"/>
<rect x="462" width="9" height="534" fill="black"/>
<rect x="483" width="9" height="534" fill="black"/>
<rect x="504" width="9" height="534" fill="black"/>
<rect x="525" width="9" height="534" fill="black"/>
<rect x="252" width="9" height="534" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 KiB

View File

@@ -0,0 +1,460 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Captured Sun</title>
<link rel="icon" href="/_/logo.svg">
<style>
@font-face {
font-family: 'IMFell';
src: url('/_/IMFell/IMFell.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
*, *::before, *::after { box-sizing: border-box; }
body {
overflow: hidden;
margin: 0;
padding: 0;
background: #000;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; }
img {
user-select: none;
-webkit-user-drag: none;
pointer-events: none;
}
a:visited {
color: inherit;
}
@keyframes rotate-stars {
0% { transform: rotate(0deg) translateX(0); }
25% { transform: rotate(90deg) translateX(50px); }
50% { transform: rotate(180deg) translateX(0); }
75% { transform: rotate(270deg) translateX(-50px); }
100% { transform: rotate(360deg) translateX(0); }
}
.stars {
display: block;
height: auto;
position: absolute;
animation: rotate-stars 950s linear infinite;
transform-origin: center center;
top: -550px;
width: 1600px;
z-index: 0;
}
.moon {
position: fixed;
z-index: 2;
width: 100vw;
left: 0px;
bottom: 0px; /* seam overlap — tweak to taste */
}
.atlas {
position: fixed;
left: 50%;
bottom: -100px;
transform: translateX(-50%);
width: 85vw;
height: auto;
z-index: 3;
pointer-events: none;
}
.atlas img { display: block; width: 100%; height: auto; }
.nav {
position: fixed;
bottom: 440px; left: 0; right: 0;
display: flex; justify-content: center; gap: 18px;
z-index: 4;
}
.btn {
padding: 12px 20px;
border-radius: 11px;
font-size: 16px;
font-weight: 600;
letter-spacing: 0.02em;
cursor: pointer;
font-family: 'IMFell';
backdrop-filter: blur(14px) saturate(160%);
-webkit-backdrop-filter: blur(14px) saturate(160%);
box-shadow:
inset 0 0.5px 0 rgba(255, 255, 255, 0.35),
inset 0 -1px 0 rgba(255, 255, 255, 0.08),
0 8px 24px rgba(0, 0, 0, 0.25);
transition: transform 120ms ease, box-shadow 120ms ease;
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn-red {
background: rgba(220, 38, 38, 0.30);
color: #d06868;
border: 0.5px solid rgba(254, 202, 202, 0.05);
}
.btn-orange {
background: rgba(234, 88, 12, 0.30);
color: #d79d5a;
border: 0.5px solid rgba(254, 215, 170, 0.05);
}
.btn-yellow {
background: rgba(234, 179, 8, 0.30);
color: #d0c04b;
border: 0.5px solid rgba(254, 240, 138, 0.05);
}
.menu {
position: fixed;
border-radius: 11px;
overflow: hidden;
z-index: 10;
backdrop-filter: blur(14px) saturate(160%);
-webkit-backdrop-filter: blur(14px) saturate(160%);
box-shadow:
inset 0 0.5px 0 rgba(255, 255, 255, 0.35),
inset 0 -1px 0 rgba(255, 255, 255, 0.08),
0 8px 24px rgba(0, 0, 0, 0.25);
transition:
top 460ms cubic-bezier(0.32, 0.72, 0.20, 1),
left 460ms cubic-bezier(0.32, 0.72, 0.20, 1),
width 460ms cubic-bezier(0.32, 0.72, 0.20, 1),
height 460ms cubic-bezier(0.32, 0.72, 0.20, 1),
border-radius 460ms cubic-bezier(0.32, 0.72, 0.20, 1);
visibility: hidden;
pointer-events: none;
font-family: 'IMFell';
}
.menu.is-active { visibility: visible; pointer-events: auto; }
.menu-red { background: rgba(220, 38, 38, 0.30); color: #fab2b2; border: 0.5px solid rgba(254, 202, 202, 0.05); }
.menu-orange { background: rgba(234, 88, 12, 0.30); color: #d79d5a; border: 0.5px solid rgba(254, 215, 170, 0.05); }
.menu-yellow { background: rgba(234, 179, 8, 0.30); color: #d0c04b; border: 0.5px solid rgba(254, 240, 138, 0.05); }
.menu-close {
position: absolute;
top: 18px; right: 18px;
width: 36px; height: 36px;
border-radius: 50%;
border: 0.5px solid rgba(255, 255, 255, 0.10);
background: rgba(0, 0, 0, 0.20);
color: inherit;
font-size: 22px;
line-height: 1;
cursor: pointer;
display: flex; align-items: center; justify-content: center;
opacity: 0;
transition: opacity 180ms ease, background 150ms ease;
z-index: 2;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
}
.menu-close:hover { background: rgba(0, 0, 0, 0.35); }
.menu.is-open .menu-close { opacity: 1; }
.menu-inner {
padding: 72px 56px 56px;
opacity: 0;
transform: translateY(8px);
transition: opacity 220ms ease, transform 220ms ease;
height: 100%;
overflow-y: auto;
color: inherit;
}
.menu.is-open .menu-inner {
opacity: 1;
transform: translateY(0);
transition-delay: 280ms;
}
.menu h2 {
margin: 0 0 24px;
font-size: 36px;
font-weight: 400;
letter-spacing: 0.02em;
}
.menu p {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
font-size: 16px;
line-height: 1.65;
margin: 0 0 16px;
max-width: 62ch;
}
.link-list {
list-style: none;
padding: 0;
margin: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 12px;
}
.link-list a {
display: block;
padding: 16px 18px;
background: rgba(0, 0, 0, 0.15);
border: 0.5px solid rgba(255, 255, 255, 0.06);
border-radius: 10px;
color: inherit;
text-decoration: none;
transition: background 150ms ease, transform 150ms ease;
}
.link-list a:hover { background: rgba(0, 0, 0, 0.30); transform: translateY(-1px); }
.link-list .label { display: block; font-size: 17px; }
.link-list .desc {
display: block;
font-size: 13px;
opacity: 0.7;
margin-top: 4px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
}
.contact-form {
display: grid;
gap: 16px;
max-width: 560px;
}
.contact-form label {
display: grid;
gap: 6px;
font-size: 13px;
letter-spacing: 0.08em;
text-transform: uppercase;
opacity: 0.85;
}
.contact-form input,
.contact-form textarea {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
font-size: 15px;
color: #f0e6c8;
padding: 12px 14px;
background: rgba(0, 0, 0, 0.50);
border: 0.5px solid rgba(255, 255, 255, 0.08);
border-radius: 8px;
outline: none;
transition: border-color 150ms ease, background 150ms ease;
}
.contact-form input::placeholder,
.contact-form textarea::placeholder { color: rgba(240, 230, 200, 0.35); }
.contact-form input:focus,
.contact-form textarea:focus {
border-color: rgba(255, 255, 255, 0.25);
background: rgba(0, 0, 0, 0.65);
}
.contact-form textarea { resize: vertical; min-height: 140px; }
.contact-form .submit {
justify-self: end;
margin-top: 4px;
padding: 12px 26px;
border-radius: 10px;
border: 0.5px solid rgba(254, 240, 138, 0.10);
background: rgba(234, 179, 8, 0.40);
color: inherit;
font-family: 'IMFell';
font-size: 16px;
letter-spacing: 0.04em;
cursor: pointer;
transition: transform 120ms ease, background 120ms ease;
}
.contact-form .submit:hover { background: rgba(234, 179, 8, 0.55); transform: translateY(-1px); }
@media (max-width: 768px) {
.space {
position: absolute;
transform: translate(-50%, 0);
top: 0px;
display:none;
width: 1200px;
}
.moon {
display: none;
}
.stars {
display: none;
}
.atlas {
top: -200px;
width: 1000px;
}
.nav {
bottom: 1vh
}
.menu-red {
color: rgb(255, 255, 255) !important
}
.menu-inner { padding: 64px 28px 28px; }
.menu h2 { font-size: 28px; }
}
</style>
</head>
<body style="cursor: default">
<img class="stars" src="/_/stars.png" alt="">
<img class="moon" src="/_/moon.png" alt="">
<picture class="atlas">
<source media="(max-width: 768px)" srcset="/_/atlasmobile.png">
<img src="/_/atlas.webp" alt="">
</picture>
<nav class="nav">
<button class="btn btn-red">About</button>
<!-- <button class="btn btn-orange">Our Work</button> -->
<button class="btn btn-orange">Contact Us</button>
</nav>
<div class="menu menu-red" id="menu-about">
<button class="menu-close" aria-label="Close">X</button>
<div class="menu-inner">
<h2>About</h2>
<p><i>All energy comes from the sun. To think is to capture, freeze, and reflect it.</i></p>
<p>Captured Sun was founded in 2022, to answer a simple question: what would the operating system look like if it was rearranged for the modern day?</p>
<p>The answer we have found is <a style="color: inherit;" href="https://frm.so" target="_blank">Forum</a>: the Community Operating System.</p>
<p>In concert with Forum, we are happy to accept certain consulting jobs with people or organizations that need custom solutions. If that's you, feel free to reach out.</p>
</div>
</div>
<div class="menu menu-orange" id="menu-work">
<button class="menu-close" aria-label="Close">X</button>
<div class="menu-inner">
<h2>Our Work</h2>
<ul class="link-list">
<li><a href="https://apple.com" target="_blank" rel="noopener"><span class="label">Apple</span><span class="desc">Product launch microsite</span></a></li>
<li><a href="https://vercel.com" target="_blank" rel="noopener"><span class="label">Vercel</span><span class="desc">Edge runtime documentation</span></a></li>
<li><a href="https://linear.app" target="_blank" rel="noopener"><span class="label">Linear</span><span class="desc">Marketing site redesign</span></a></li>
<li><a href="https://figma.com" target="_blank" rel="noopener"><span class="label">Figma</span><span class="desc">Plugin gallery</span></a></li>
<li><a href="https://stripe.com" target="_blank" rel="noopener"><span class="label">Stripe</span><span class="desc">Developer onboarding flow</span></a></li>
<li><a href="https://railway.com" target="_blank" rel="noopener"><span class="label">Railway</span><span class="desc">Pricing page experiments</span></a></li>
<li><a href="https://anthropic.com" target="_blank" rel="noopener"><span class="label">Anthropic</span><span class="desc">Research publication template</span></a></li>
<li><a href="https://notion.so" target="_blank" rel="noopener"><span class="label">Notion</span><span class="desc">Template marketplace</span></a></li>
</ul>
</div>
</div>
<div class="menu menu-orange" id="menu-contact">
<button class="menu-close" aria-label="Close"><span>X</span></button>
<div class="menu-inner">
<h2>Contact Us</h2>
<form class="contact-form" novalidate onsubmit="return handleContactSubmit(event, this)">
<label>Name <input type="text" name="name" placeholder="Your name" required></label>
<label>Email <input type="email" name="email" placeholder="you@example.com"></label>
<label>Phone <input type="tel" name="phone" placeholder="+1 555 123 4567"></label>
<label>Message <textarea name="message" placeholder="Tell us about your project..." required></textarea></label>
<button class="submit" type="submit">Send Message</button>
</form>
</div>
</div>
<script>
function handleContactSubmit(event, form) {
event.preventDefault();
var email = form.email.value.trim();
var phone = form.phone.value.trim();
if (!email && !phone) {
form.email.setCustomValidity('Provide an email or a phone number.');
form.email.reportValidity();
return false;
}
form.email.setCustomValidity('');
if (!form.checkValidity()) { form.reportValidity(); return false; }
form.reset();
return false;
}
(function () {
var pairs = [
{ btn: document.querySelector('.btn-red'), menu: document.getElementById('menu-about') },
{ btn: document.querySelector('.btn-orange'), menu: document.getElementById('menu-contact') }
];
var active = null;
var FINAL_RADIUS = '18px';
function applyRect(menu, rect) {
menu.style.top = rect.top + 'px';
menu.style.left = rect.left + 'px';
menu.style.width = rect.width + 'px';
menu.style.height = rect.height + 'px';
menu.style.borderRadius = '11px';
}
function applyFinal(menu) {
menu.style.top = '10vh';
menu.style.left = '10vw';
menu.style.width = '80vw';
menu.style.height = '80vh';
menu.style.borderRadius = FINAL_RADIUS;
}
function openMenu(pair) {
if (active) closeMenu();
var btn = pair.btn, menu = pair.menu;
if (menu._closeTimer) { clearTimeout(menu._closeTimer); menu._closeTimer = null; }
var rect = btn.getBoundingClientRect();
menu.style.transition = 'none';
applyRect(menu, rect);
menu.classList.add('is-active');
void menu.offsetHeight;
menu.style.transition = '';
btn.style.visibility = 'hidden';
applyFinal(menu);
menu.classList.add('is-open');
active = pair;
}
function closeMenu() {
if (!active) return;
var btn = active.btn, menu = active.menu;
var rect = btn.getBoundingClientRect();
applyRect(menu, rect);
menu.classList.remove('is-open');
if (menu._closeTimer) clearTimeout(menu._closeTimer);
menu._closeTimer = setTimeout(function () {
menu.classList.remove('is-active');
btn.style.visibility = '';
menu._closeTimer = null;
}, 460);
active = null;
}
pairs.forEach(function (pair) {
pair.btn.addEventListener('click', function (e) {
e.stopPropagation();
openMenu(pair);
});
pair.menu.addEventListener('click', function (e) { e.stopPropagation(); });
pair.menu.querySelector('.menu-close').addEventListener('click', function (e) {
e.stopPropagation();
closeMenu();
});
});
document.addEventListener('click', function () {
if (active) closeMenu();
});
window.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && active) closeMenu();
});
window.addEventListener('resize', function () {
if (!active) return;
applyFinal(active.menu);
});
})();
</script>
</body>
</html>

5
priv/schema.sql Normal file
View File

@@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS messages (
id BIGSERIAL PRIMARY KEY,
text TEXT NOT NULL,
inserted_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

496
priv/ui/admin.html Normal file
View File

@@ -0,0 +1,496 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>admin</title>
<link rel="icon" href="/admin/graphyellow.svg">
<style>
body {
background: #850000;
color: #FEBA7D;
font: 12px ui-monospace, monospace;
margin: 1rem;
}
.tabs { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 1rem; }
.tabs button {
color: #FEBA7D; background: none; border: 1px solid transparent; font: inherit;
cursor: pointer; padding: 4px 10px; border-radius: 3px;
}
.tabs button:hover { background: #9D1A12; border-color: #C7643F; }
.tabs button.active {
font-weight: bold; color: #850000; background: #FEBA7D;
border-color: #FEBA7D; cursor: default;
}
.toolbar { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem; }
.toolbar button {
color: #FEBA7D; background: none; border: none; font: inherit;
cursor: pointer; padding: 0 2px;
}
.toolbar button:hover { color: #FEBA7D; text-decoration: underline; }
.toolbar button.active { font-weight: bold; color: #FEBA7D; cursor: default; }
.toolbar input {
background: #6C0000; color: #FEBA7D; font: inherit;
padding: 2px 6px; border: 1px solid #C7643F;
border-radius: 3px; width: 16rem;
}
.toolbar input::placeholder { color: #E49768; }
table { border-collapse: collapse; width: 100%; }
th, td { padding: 4px 8px; border-bottom: 1px solid rgba(254, 232, 200, 0.2); text-align: left; vertical-align: top; }
td { color: #FEBA7D; }
th { background: #6C0000; color: #FEBA7D; position: sticky; top: 0; }
tr:hover { background: #9D1A12; }
.num { text-align: right; }
.name, .mod { color: #ffe1c6; }
#status { margin-left: auto; color: #FEBA7D; }
.disclosure {
display: inline-block; width: 1em; cursor: pointer;
color: #FEBA7D; user-select: none;
}
.disclosure.leaf { cursor: default; visibility: hidden; }
.pid-cell { white-space: pre; }
section[hidden] { display: none; }
</style>
<script src="https://frm.so/_/code/quill.js"></script>
</head>
<body>
<nav class="tabs">
<button id="tab-processes" class="active">processes</button>
<button id="tab-modules">modules</button>
<button id="tab-logs">logs</button>
<button id="tab-vm-memory">vm memory</button>
<span id="status">connecting…</span>
</nav>
<section id="panel-processes">
<div class="toolbar">
<button id="p-mine" class="active">mine</button>
<button id="p-all">all</button>
<span>|</span>
<button id="p-expand">expand all</button>
<button id="p-collapse">collapse all</button>
</div>
<table>
<thead><tr>
<th>pid</th><th>name</th><th>initial call</th>
<th class="num">memory (KB)</th><th class="num">tree memory (KB)</th>
<th class="num">msgs</th><th>status</th>
</tr></thead>
<tbody id="p-rows"></tbody>
</table>
</section>
<section id="panel-modules" hidden>
<div class="toolbar">
<button id="m-mine" class="active">mine</button>
<button id="m-all">all</button>
<input id="m-filter" placeholder="filter…" autocomplete="off">
<button id="m-refresh">refresh</button>
</div>
<table>
<thead><tr>
<th>module</th><th>app</th><th>source</th>
</tr></thead>
<tbody id="m-rows"></tbody>
</table>
</section>
<section id="panel-logs" hidden>
<div class="toolbar">
<button id="l-refresh">refresh</button>
</div>
<table>
<thead><tr>
<th>time (Chicago)</th><th>source ip</th><th>host</th><th>method</th><th>path</th>
<th class="num">status</th><th class="num">duration ms</th>
</tr></thead>
<tbody id="l-rows"></tbody>
</table>
</section>
<section id="panel-vm-memory" hidden>
<div class="toolbar">
<button id="v-refresh">refresh</button>
</div>
<table>
<thead><tr>
<th>category</th><th class="num">bytes</th>
<th class="num">KB</th><th class="num">MB</th>
</tr></thead>
<tbody id="v-rows"></tbody>
</table>
</section>
<script>
// ── shared infrastructure ──────────────────────────────────────
const status = document.getElementById("status");
const url = (location.protocol === "https:" ? "wss://" : "ws://") + location.host + "/admin/ws";
const ws = new WebSocket(url);
// Per-message-type dispatch so each panel registers its own handler.
const handlers = {};
ws.addEventListener("message", (e) => {
const msg = JSON.parse(e.data);
const h = handlers[msg.type];
if (h) h(msg);
});
function send(obj) {
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(obj));
}
function addCell(tr, text, cls) {
const td = document.createElement("td");
if (cls) td.className = cls;
td.textContent = text;
tr.appendChild(td);
}
// ── tabs ───────────────────────────────────────────────────────
const tabs = {
processes: { btn: document.getElementById("tab-processes"), panel: document.getElementById("panel-processes") },
modules: { btn: document.getElementById("tab-modules"), panel: document.getElementById("panel-modules") },
logs: { btn: document.getElementById("tab-logs"), panel: document.getElementById("panel-logs") },
vmMemory: { btn: document.getElementById("tab-vm-memory"), panel: document.getElementById("panel-vm-memory") },
};
const tabRoutes = {
processes: "processes",
modules: "modules",
logs: "logs",
vmMemory: "vm-memory",
};
let active = "processes";
function tabFromLocation() {
const segment = location.pathname.split("/").filter(Boolean)[1];
return Object.keys(tabRoutes).find(name => tabRoutes[name] === segment) || "processes";
}
function setAdminPath(name, mode) {
const nextPath = "/admin/" + tabRoutes[name];
if (location.pathname === nextPath) return;
const nextUrl = nextPath + location.search + location.hash;
if (mode === "replace") history.replaceState({tab: name}, "", nextUrl);
else history.pushState({tab: name}, "", nextUrl);
}
function activate(name, mode) {
active = name;
setAdminPath(name, mode);
for (const [k, t] of Object.entries(tabs)) {
t.btn.classList.toggle("active", k === name);
t.panel.hidden = k !== name;
}
if (name === "modules" && !mLoaded) pollModules();
if (name === "logs" && !lLoaded) pollLogs();
if (name === "vmMemory" && !vLoaded) pollVmMemory();
}
tabs.processes.btn.addEventListener("click", () => activate("processes"));
tabs.modules.btn.addEventListener("click", () => activate("modules"));
tabs.logs.btn.addEventListener("click", () => activate("logs"));
tabs.vmMemory.btn.addEventListener("click", () => activate("vmMemory"));
window.addEventListener("popstate", () => activate(tabFromLocation(), "replace"));
// ── processes panel ────────────────────────────────────────────
const pBody = document.getElementById("p-rows");
const pAll = document.getElementById("p-all");
const pMine = document.getElementById("p-mine");
const pExpand = document.getElementById("p-expand");
const pCollapse = document.getElementById("p-collapse");
let pFilterMine = $("#p-mine").classList.contains("active");
let pLastRows = [];
const pCollapsed = new Set();
const pAutoCollapsed = new Set();
const pSeenLarge = new Set();
function pSetFilter(mine) {
pFilterMine = mine;
pAll.classList.toggle("active", !mine);
pMine.classList.toggle("active", mine);
pRender();
}
pAll.addEventListener("click", () => pSetFilter(false));
pMine.addEventListener("click", () => pSetFilter(true));
pExpand.addEventListener("click", () => {
pCollapsed.clear();
pAutoCollapsed.clear();
for (const pid of pLargeGroupPids()) pSeenLarge.add(pid);
pRender();
});
pCollapse.addEventListener("click", () => {
const haveChildren = new Set(pLastRows.map(r => r.parent).filter(Boolean));
const topPid = pPrimaryRootProcessPid();
pCollapsed.clear();
pAutoCollapsed.clear();
for (const pid of haveChildren) {
if (pid !== topPid) pCollapsed.add(pid);
}
pRender();
});
function pBuildTree(rows) {
const byPid = new Map();
for (const r of rows) byPid.set(r.pid, Object.assign({}, r, {children: []}));
const roots = [];
for (const node of byPid.values()) {
const parent = node.parent && byPid.get(node.parent);
if (parent) parent.children.push(node);
else roots.push(node);
}
return roots;
}
function pRender() {
const rows = pFilterMine ? pLastRows.filter(r => r.mine) : pLastRows;
const tree = pBuildTree(rows);
const mineN = pLastRows.filter(r => r.mine).length;
pAll.textContent = "all (" + pLastRows.length + ")";
pMine.textContent = "mine (" + mineN + ")";
const frag = document.createDocumentFragment();
for (const node of tree) pRenderNode(node, 0, frag);
pBody.replaceChildren(frag);
}
function pRenderNode(node, depth, frag) {
const tr = document.createElement("tr");
if (node.mine) tr.className = "mine";
const pidTd = document.createElement("td");
pidTd.className = "pid-cell";
pidTd.style.paddingLeft = (8 + depth * 16) + "px";
const disc = document.createElement("span");
disc.className = "disclosure" + (node.children.length ? "" : " leaf");
disc.textContent =
node.children.length === 0 ? "·" :
pCollapsed.has(node.pid) ? "▸" : "▾";
if (node.children.length) {
disc.addEventListener("click", () => {
pSeenLarge.add(node.pid);
pAutoCollapsed.delete(node.pid);
if (pCollapsed.has(node.pid)) pCollapsed.delete(node.pid);
else pCollapsed.add(node.pid);
pRender();
});
}
pidTd.appendChild(disc);
pidTd.appendChild(document.createTextNode(" " + node.pid));
tr.appendChild(pidTd);
addCell(tr, node.name || "-", node.name ? "name" : "");
addCell(tr, node.initial_call);
addCell(tr, node.memory_kb, "num");
addCell(tr, node.tree_memory_kb || node.memory_kb, "num");
addCell(tr, node.msgs, "num");
addCell(tr, node.status);
frag.appendChild(tr);
if (!pCollapsed.has(node.pid)) {
for (const child of node.children) pRenderNode(child, depth + 1, frag);
}
}
function pollProcesses() { send({type: "list_processes"}); }
handlers["processes"] = (msg) => {
pLastRows = msg.rows;
pAutoCollapseLargeGroups();
pRender();
};
function pLargeGroupPids() {
const large = new Set();
const visit = (node) => {
if (node.children.length > 10) large.add(node.pid);
for (const child of node.children) visit(child);
};
for (const node of pBuildTree(pLastRows)) visit(node);
return large;
}
function pPrimaryRootProcessPid() {
const supervisor = pLastRows.find(r => r.name === "Forum.Supervisor");
if (supervisor) return supervisor.pid;
const roots = pBuildTree(pLastRows);
return roots.length ? roots[0].pid : null;
}
function pAutoCollapseLargeGroups() {
const large = pLargeGroupPids();
const topPid = pPrimaryRootProcessPid();
if (topPid) {
large.delete(topPid);
pCollapsed.delete(topPid);
pAutoCollapsed.delete(topPid);
pSeenLarge.add(topPid);
}
for (const pid of pAutoCollapsed) {
if (!large.has(pid)) {
pCollapsed.delete(pid);
pAutoCollapsed.delete(pid);
}
}
for (const pid of large) {
if (!pSeenLarge.has(pid)) {
pCollapsed.add(pid);
pAutoCollapsed.add(pid);
pSeenLarge.add(pid);
}
}
}
// ── modules panel ──────────────────────────────────────────────
const mBody = document.getElementById("m-rows");
const mAll = document.getElementById("m-all");
const mMine = document.getElementById("m-mine");
const mFilter = document.getElementById("m-filter");
const mRefresh = document.getElementById("m-refresh");
let mFilterMine = $("#m-mine").classList.contains("active");
let mNeedle = "";
let mLastRows = [];
let mLoaded = false;
function mSetFilter(mine) {
mFilterMine = mine;
mAll.classList.toggle("active", !mine);
mMine.classList.toggle("active", mine);
mRender();
}
mAll.addEventListener("click", () => mSetFilter(false));
mMine.addEventListener("click", () => mSetFilter(true));
mFilter.addEventListener("input", () => { mNeedle = mFilter.value.toLowerCase(); mRender(); });
mRefresh.addEventListener("click", () => pollModules());
function mRender() {
let rows = mLastRows;
if (mFilterMine) rows = rows.filter(r => r.mine);
if (mNeedle) {
rows = rows.filter(r =>
r.module.toLowerCase().includes(mNeedle) ||
(r.source && r.source.toLowerCase().includes(mNeedle)) ||
(r.app && r.app.toLowerCase().includes(mNeedle))
);
}
const mineN = mLastRows.filter(r => r.mine).length;
mAll.textContent = "all (" + mLastRows.length + ")";
mMine.textContent = "mine (" + mineN + ")";
const frag = document.createDocumentFragment();
for (const r of rows) {
const tr = document.createElement("tr");
if (r.mine) tr.className = "mine";
addCell(tr, r.module, "mod");
addCell(tr, r.app || "-");
addCell(tr, r.source || "(no source)", "src");
frag.appendChild(tr);
}
mBody.replaceChildren(frag);
}
function pollModules() { send({type: "list_modules"}); }
handlers["modules"] = (msg) => {
mLastRows = msg.rows;
mLoaded = true;
mRender();
};
// ── logs panel ─────────────────────────────────────────────────
const lBody = document.getElementById("l-rows");
const lRefresh = document.getElementById("l-refresh");
let lLoaded = false;
const chicagoTimeParts = new Intl.DateTimeFormat("en-US", {
timeZone: "America/Chicago",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "2-digit",
second: "2-digit",
hour12: true
});
function pollLogs() { send({type: "list_logs"}); }
function formatLogTime(value) {
if (!value) return "";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
const parts = Object.fromEntries(
chicagoTimeParts.formatToParts(date).map(part => [part.type, part.value])
);
return `${parts.month}.${parts.day} ${parts.hour}:${parts.minute}:${parts.second}${parts.dayPeriod.toLowerCase()}`;
}
lRefresh.addEventListener("click", () => pollLogs());
handlers["logs"] = (msg) => {
lLoaded = true;
const frag = document.createDocumentFragment();
for (const r of msg.rows) {
const tr = document.createElement("tr");
addCell(tr, formatLogTime(r.time));
addCell(tr, r.source_ip || "");
addCell(tr, r.host || "");
addCell(tr, r.method || "");
addCell(tr, (r.path || "") + (r.query_string ? "?" + r.query_string : ""));
addCell(tr, r.status || "", "num");
addCell(tr, r.duration_ms || "", "num");
frag.appendChild(tr);
}
lBody.replaceChildren(frag);
};
// ── VM memory panel ────────────────────────────────────────────
const vBody = document.getElementById("v-rows");
const vRefresh = document.getElementById("v-refresh");
let vLoaded = false;
function pollVmMemory() { send({type: "list_vm_memory"}); }
vRefresh.addEventListener("click", () => pollVmMemory());
handlers["vm_memory"] = (msg) => {
vLoaded = true;
const frag = document.createDocumentFragment();
for (const r of msg.rows) {
const tr = document.createElement("tr");
addCell(tr, r.category || "");
addCell(tr, r.bytes || "", "num");
addCell(tr, r.kb || "", "num");
addCell(tr, r.mb || "", "num");
frag.appendChild(tr);
}
vBody.replaceChildren(frag);
};
// ── boot ───────────────────────────────────────────────────────
activate(tabFromLocation(), "replace");
ws.addEventListener("open", () => {
status.textContent = "connected — processes auto-refresh 5s";
pollProcesses();
if (active === "modules") pollModules();
if (active === "logs") pollLogs();
if (active === "vmMemory") pollVmMemory();
// Processes keep polling whether or not the tab is visible — keeps
// the view fresh when the user switches back. Modules load on
// first activation and via the refresh button.
setInterval(pollProcesses, 5000);
});
ws.addEventListener("close", () => { status.textContent = "disconnected"; });
ws.addEventListener("error", () => { status.textContent = "error"; });
</script>
</body>
</html>

490
priv/ui/admin.pug Normal file
View File

@@ -0,0 +1,490 @@
doctype html
html(lang="en")
head
meta(charset="utf-8")
title admin
link(rel="icon", href="/graphyellow.svg")
style.
body {
background: #850000;
color: #FEBA7D;
font: 12px ui-monospace, monospace;
margin: 1rem;
}
.tabs { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 1rem; }
.tabs button {
color: #FEBA7D; background: none; border: 1px solid transparent; font: inherit;
cursor: pointer; padding: 4px 10px; border-radius: 3px;
}
.tabs button:hover { background: #9D1A12; border-color: #C7643F; }
.tabs button.active {
font-weight: bold; color: #850000; background: #FEBA7D;
border-color: #FEBA7D; cursor: default;
}
.toolbar { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem; }
.toolbar button {
color: #FEBA7D; background: none; border: none; font: inherit;
cursor: pointer; padding: 0 2px;
}
.toolbar button:hover { color: #FEBA7D; text-decoration: underline; }
.toolbar button.active { font-weight: bold; color: #FEBA7D; cursor: default; }
.toolbar input {
background: #6C0000; color: #FEBA7D; font: inherit;
padding: 2px 6px; border: 1px solid #C7643F;
border-radius: 3px; width: 16rem;
}
.toolbar input::placeholder { color: #E49768; }
table { border-collapse: collapse; width: 100%; }
th, td { padding: 4px 8px; border-bottom: 0.5px solid rgba(254, 232, 200, 0.2); text-align: left; vertical-align: top; }
td { color: #5C0000; }
th { background: #6C0000; color: #FEBA7D; position: sticky; top: 0; }
tr:hover { background: #9D1A12; }
.num { text-align: right; }
.name, .mod { color: #FEBA7D; }
.src { color: #5C0000; }
#status { margin-left: auto; color: #FEBA7D; }
.disclosure {
display: inline-block; width: 1em; cursor: pointer;
color: #FEBA7D; user-select: none;
}
.disclosure.leaf { cursor: default; visibility: hidden; }
.pid-cell { white-space: pre; }
section[hidden] { display: none; }
script(src="https://frm.so/_/code/quill.js")
body
nav.tabs
button#tab-processes.active processes
button#tab-modules modules
button#tab-logs logs
button#tab-vm-memory vm memory
span#status connecting…
section#panel-processes
.toolbar
button#p-mine.active mine
button#p-all all
span |
button#p-expand expand all
button#p-collapse collapse all
table
thead
tr
th pid
th name
th initial call
th.num memory (KB)
th.num tree memory (KB)
th.num msgs
th status
tbody#p-rows
section#panel-modules(hidden)
.toolbar
button#m-mine.active mine
button#m-all all
input#m-filter(placeholder="filter…", autocomplete="off")
button#m-refresh refresh
table
thead
tr
th module
th app
th source
tbody#m-rows
section#panel-logs(hidden)
.toolbar
button#l-refresh refresh
table
thead
tr
th time (Chicago)
th source ip
th host
th method
th path
th.num status
th.num duration ms
tbody#l-rows
section#panel-vm-memory(hidden)
.toolbar
button#v-refresh refresh
table
thead
tr
th category
th.num bytes
th.num KB
th.num MB
tbody#v-rows
script.
// ── shared infrastructure ──────────────────────────────────────
const status = document.getElementById("status");
const url = (location.protocol === "https:" ? "wss://" : "ws://") + location.host + "/admin/ws";
const ws = new WebSocket(url);
// Per-message-type dispatch so each panel registers its own handler.
const handlers = {};
ws.addEventListener("message", (e) => {
const msg = JSON.parse(e.data);
const h = handlers[msg.type];
if (h) h(msg);
});
function send(obj) {
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(obj));
}
function addCell(tr, text, cls) {
const td = document.createElement("td");
if (cls) td.className = cls;
td.textContent = text;
tr.appendChild(td);
}
// ── tabs ───────────────────────────────────────────────────────
const tabs = {
processes: { btn: document.getElementById("tab-processes"), panel: document.getElementById("panel-processes") },
modules: { btn: document.getElementById("tab-modules"), panel: document.getElementById("panel-modules") },
logs: { btn: document.getElementById("tab-logs"), panel: document.getElementById("panel-logs") },
vmMemory: { btn: document.getElementById("tab-vm-memory"), panel: document.getElementById("panel-vm-memory") },
};
const tabRoutes = {
processes: "processes",
modules: "modules",
logs: "logs",
vmMemory: "vm-memory",
};
let active = "processes";
function tabFromLocation() {
const segment = location.pathname.split("/").filter(Boolean)[1];
return Object.keys(tabRoutes).find(name => tabRoutes[name] === segment) || "processes";
}
function setAdminPath(name, mode) {
const nextPath = "/admin/" + tabRoutes[name];
if (location.pathname === nextPath) return;
const nextUrl = nextPath + location.search + location.hash;
if (mode === "replace") history.replaceState({tab: name}, "", nextUrl);
else history.pushState({tab: name}, "", nextUrl);
}
function activate(name, mode) {
active = name;
setAdminPath(name, mode);
for (const [k, t] of Object.entries(tabs)) {
t.btn.classList.toggle("active", k === name);
t.panel.hidden = k !== name;
}
if (name === "modules" && !mLoaded) pollModules();
if (name === "logs" && !lLoaded) pollLogs();
if (name === "vmMemory" && !vLoaded) pollVmMemory();
}
tabs.processes.btn.addEventListener("click", () => activate("processes"));
tabs.modules.btn.addEventListener("click", () => activate("modules"));
tabs.logs.btn.addEventListener("click", () => activate("logs"));
tabs.vmMemory.btn.addEventListener("click", () => activate("vmMemory"));
window.addEventListener("popstate", () => activate(tabFromLocation(), "replace"));
// ── processes panel ────────────────────────────────────────────
const pBody = document.getElementById("p-rows");
const pAll = document.getElementById("p-all");
const pMine = document.getElementById("p-mine");
const pExpand = document.getElementById("p-expand");
const pCollapse = document.getElementById("p-collapse");
let pFilterMine = $("#p-mine").classList.contains("active");
let pLastRows = [];
const pCollapsed = new Set();
const pAutoCollapsed = new Set();
const pSeenLarge = new Set();
function pSetFilter(mine) {
pFilterMine = mine;
pAll.classList.toggle("active", !mine);
pMine.classList.toggle("active", mine);
pRender();
}
pAll.addEventListener("click", () => pSetFilter(false));
pMine.addEventListener("click", () => pSetFilter(true));
pExpand.addEventListener("click", () => {
pCollapsed.clear();
pAutoCollapsed.clear();
for (const pid of pLargeGroupPids()) pSeenLarge.add(pid);
pRender();
});
pCollapse.addEventListener("click", () => {
const haveChildren = new Set(pLastRows.map(r => r.parent).filter(Boolean));
const topPid = pPrimaryRootProcessPid();
pCollapsed.clear();
pAutoCollapsed.clear();
for (const pid of haveChildren) {
if (pid !== topPid) pCollapsed.add(pid);
}
pRender();
});
function pBuildTree(rows) {
const byPid = new Map();
for (const r of rows) byPid.set(r.pid, Object.assign({}, r, {children: []}));
const roots = [];
for (const node of byPid.values()) {
const parent = node.parent && byPid.get(node.parent);
if (parent) parent.children.push(node);
else roots.push(node);
}
return roots;
}
function pRender() {
const rows = pFilterMine ? pLastRows.filter(r => r.mine) : pLastRows;
const tree = pBuildTree(rows);
const mineN = pLastRows.filter(r => r.mine).length;
pAll.textContent = "all (" + pLastRows.length + ")";
pMine.textContent = "mine (" + mineN + ")";
const frag = document.createDocumentFragment();
for (const node of tree) pRenderNode(node, 0, frag);
pBody.replaceChildren(frag);
}
function pRenderNode(node, depth, frag) {
const tr = document.createElement("tr");
if (node.mine) tr.className = "mine";
const pidTd = document.createElement("td");
pidTd.className = "pid-cell";
pidTd.style.paddingLeft = (8 + depth * 16) + "px";
const disc = document.createElement("span");
disc.className = "disclosure" + (node.children.length ? "" : " leaf");
disc.textContent =
node.children.length === 0 ? "·" :
pCollapsed.has(node.pid) ? "▸" : "▾";
if (node.children.length) {
disc.addEventListener("click", () => {
pSeenLarge.add(node.pid);
pAutoCollapsed.delete(node.pid);
if (pCollapsed.has(node.pid)) pCollapsed.delete(node.pid);
else pCollapsed.add(node.pid);
pRender();
});
}
pidTd.appendChild(disc);
pidTd.appendChild(document.createTextNode(" " + node.pid));
tr.appendChild(pidTd);
addCell(tr, node.name || "-", node.name ? "name" : "");
addCell(tr, node.initial_call);
addCell(tr, node.memory_kb, "num");
addCell(tr, node.tree_memory_kb || node.memory_kb, "num");
addCell(tr, node.msgs, "num");
addCell(tr, node.status);
frag.appendChild(tr);
if (!pCollapsed.has(node.pid)) {
for (const child of node.children) pRenderNode(child, depth + 1, frag);
}
}
function pollProcesses() { send({type: "list_processes"}); }
handlers["processes"] = (msg) => {
pLastRows = msg.rows;
pAutoCollapseLargeGroups();
pRender();
};
function pLargeGroupPids() {
const large = new Set();
const visit = (node) => {
if (node.children.length > 10) large.add(node.pid);
for (const child of node.children) visit(child);
};
for (const node of pBuildTree(pLastRows)) visit(node);
return large;
}
function pPrimaryRootProcessPid() {
const supervisor = pLastRows.find(r => r.name === "Forum.Supervisor");
if (supervisor) return supervisor.pid;
const roots = pBuildTree(pLastRows);
return roots.length ? roots[0].pid : null;
}
function pAutoCollapseLargeGroups() {
const large = pLargeGroupPids();
const topPid = pPrimaryRootProcessPid();
if (topPid) {
large.delete(topPid);
pCollapsed.delete(topPid);
pAutoCollapsed.delete(topPid);
pSeenLarge.add(topPid);
}
for (const pid of pAutoCollapsed) {
if (!large.has(pid)) {
pCollapsed.delete(pid);
pAutoCollapsed.delete(pid);
}
}
for (const pid of large) {
if (!pSeenLarge.has(pid)) {
pCollapsed.add(pid);
pAutoCollapsed.add(pid);
pSeenLarge.add(pid);
}
}
}
// ── modules panel ──────────────────────────────────────────────
const mBody = document.getElementById("m-rows");
const mAll = document.getElementById("m-all");
const mMine = document.getElementById("m-mine");
const mFilter = document.getElementById("m-filter");
const mRefresh = document.getElementById("m-refresh");
let mFilterMine = $("#m-mine").classList.contains("active");
let mNeedle = "";
let mLastRows = [];
let mLoaded = false;
function mSetFilter(mine) {
mFilterMine = mine;
mAll.classList.toggle("active", !mine);
mMine.classList.toggle("active", mine);
mRender();
}
mAll.addEventListener("click", () => mSetFilter(false));
mMine.addEventListener("click", () => mSetFilter(true));
mFilter.addEventListener("input", () => { mNeedle = mFilter.value.toLowerCase(); mRender(); });
mRefresh.addEventListener("click", () => pollModules());
function mRender() {
let rows = mLastRows;
if (mFilterMine) rows = rows.filter(r => r.mine);
if (mNeedle) {
rows = rows.filter(r =>
r.module.toLowerCase().includes(mNeedle) ||
(r.source && r.source.toLowerCase().includes(mNeedle)) ||
(r.app && r.app.toLowerCase().includes(mNeedle))
);
}
const mineN = mLastRows.filter(r => r.mine).length;
mAll.textContent = "all (" + mLastRows.length + ")";
mMine.textContent = "mine (" + mineN + ")";
const frag = document.createDocumentFragment();
for (const r of rows) {
const tr = document.createElement("tr");
if (r.mine) tr.className = "mine";
addCell(tr, r.module, "mod");
addCell(tr, r.app || "-");
addCell(tr, r.source || "(no source)", "src");
frag.appendChild(tr);
}
mBody.replaceChildren(frag);
}
function pollModules() { send({type: "list_modules"}); }
handlers["modules"] = (msg) => {
mLastRows = msg.rows;
mLoaded = true;
mRender();
};
// ── logs panel ─────────────────────────────────────────────────
const lBody = document.getElementById("l-rows");
const lRefresh = document.getElementById("l-refresh");
let lLoaded = false;
const chicagoTime = new Intl.DateTimeFormat("en-US", {
timeZone: "America/Chicago",
year: "numeric",
month: "short",
day: "2-digit",
hour: "numeric",
minute: "2-digit",
second: "2-digit",
timeZoneName: "short"
});
function pollLogs() { send({type: "list_logs"}); }
function formatLogTime(value) {
if (!value) return "";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return chicagoTime.format(date);
}
lRefresh.addEventListener("click", () => pollLogs());
handlers["logs"] = (msg) => {
lLoaded = true;
const frag = document.createDocumentFragment();
for (const r of msg.rows) {
const tr = document.createElement("tr");
addCell(tr, formatLogTime(r.time));
addCell(tr, r.source_ip || "");
addCell(tr, r.host || "");
addCell(tr, r.method || "");
addCell(tr, (r.path || "") + (r.query_string ? "?" + r.query_string : ""));
addCell(tr, r.status || "", "num");
addCell(tr, r.duration_ms || "", "num");
frag.appendChild(tr);
}
lBody.replaceChildren(frag);
};
// ── VM memory panel ────────────────────────────────────────────
const vBody = document.getElementById("v-rows");
const vRefresh = document.getElementById("v-refresh");
let vLoaded = false;
function pollVmMemory() { send({type: "list_vm_memory"}); }
vRefresh.addEventListener("click", () => pollVmMemory());
handlers["vm_memory"] = (msg) => {
vLoaded = true;
const frag = document.createDocumentFragment();
for (const r of msg.rows) {
const tr = document.createElement("tr");
addCell(tr, r.category || "");
addCell(tr, r.bytes || "", "num");
addCell(tr, r.kb || "", "num");
addCell(tr, r.mb || "", "num");
frag.appendChild(tr);
}
vBody.replaceChildren(frag);
};
// ── boot ───────────────────────────────────────────────────────
activate(tabFromLocation(), "replace");
ws.addEventListener("open", () => {
status.textContent = "connected — processes auto-refresh 5s";
pollProcesses();
if (active === "modules") pollModules();
if (active === "logs") pollLogs();
if (active === "vmMemory") pollVmMemory();
// Processes keep polling whether or not the tab is visible — keeps
// the view fresh when the user switches back. Modules load on
// first activation and via the refresh button.
setInterval(pollProcesses, 5000);
});
ws.addEventListener("close", () => { status.textContent = "disconnected"; });
ws.addEventListener("error", () => { status.textContent = "error"; });

93
priv/ui/desktop.html Normal file
View File

@@ -0,0 +1,93 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>forum</title>
<style>
body { font: 14px ui-monospace, monospace; margin: 2rem; background: #f7f7f4; color: #171717; }
#status { color: #888; font-size: 12px; }
#doc { display: grid; gap: 24px; margin-top: 1rem; }
.form-table h2 { margin: 0 0 8px; font-size: 16px; }
.table-wrap { overflow-x: auto; border: 1px solid #d7d7cf; background: #fff; }
table { width: 100%; border-collapse: collapse; min-width: 920px; }
th, td { border-bottom: 1px solid #e6e6df; padding: 8px 10px; text-align: left; vertical-align: top; }
th { position: sticky; top: 0; background: #efefea; color: #555; font-size: 12px; }
tbody tr:hover { background: #fafaf7; }
td { overflow-wrap: anywhere; white-space: pre-wrap; }
code { font-weight: 700; }
</style>
</head>
<body>
<div id="status">connecting…</div>
<main id="doc"><!-- FORMS_HTML --></main>
<script>
const status = document.getElementById("status");
const doc = document.getElementById("doc");
const url = (location.protocol === "https:" ? "wss://" : "ws://") + location.host + "/ws";
let ws = null;
let heartbeat = null;
let reconnectTimer = null;
let reconnectDelay = 500;
function send(obj) {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(obj));
}
}
function stopHeartbeat() {
if (heartbeat) clearInterval(heartbeat);
heartbeat = null;
}
function scheduleReconnect() {
if (reconnectTimer) return;
status.textContent = "disconnected; reconnecting…";
stopHeartbeat();
reconnectTimer = setTimeout(() => {
reconnectTimer = null;
connect();
}, reconnectDelay);
reconnectDelay = Math.min(reconnectDelay * 2, 10000);
}
function connect() {
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
return;
}
status.textContent = "connecting…";
ws = new WebSocket(url);
ws.addEventListener("open", () => {
reconnectDelay = 500;
status.textContent = "connected";
send({type: "get_doc"});
stopHeartbeat();
heartbeat = setInterval(() => send({type: "ping"}), 25000);
});
ws.addEventListener("message", (e) => {
const msg = JSON.parse(e.data);
if (msg.type === "doc") {
doc.innerHTML = msg.html;
status.textContent = "rendered backend document at " + new Date().toLocaleTimeString();
}
});
ws.addEventListener("close", scheduleReconnect);
ws.addEventListener("error", () => {
status.textContent = "connection error; reconnecting…";
ws.close();
});
}
window.addEventListener("beforeunload", stopHeartbeat);
connect();
</script>
</body>
</html>

11
priv/ui/graphyellow.svg Normal file
View File

@@ -0,0 +1,11 @@
<svg width="30" height="28" viewBox="0 0 30 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="14.5" cy="14.5" r="6" fill="#E1FF00" stroke="#FEBA7D"/>
<circle cx="26.5" cy="3.5" r="3" fill="#E1FF00" stroke="#FEBA7D"/>
<circle cx="8.5" cy="3.5" r="3" fill="#E1FF00" stroke="#FEBA7D"/>
<circle cx="3.5" cy="19.5" r="3" fill="#E1FF00" stroke="#FEBA7D"/>
<circle cx="26.5" cy="24.5" r="3" fill="#E1FF00" stroke="#FEBA7D"/>
<rect x="19.3531" y="10.04" width="6.20598" height="0.5" transform="rotate(-42 19.3531 10.04)" fill="#E1FF00" stroke="#FEBA7D" stroke-width="0.5"/>
<rect x="11.5018" y="8.76443" width="2.48193" height="0.5" transform="rotate(-118 11.5018 8.76443)" fill="#E1FF00" stroke="#FEBA7D" stroke-width="0.5"/>
<rect x="8.74289" y="17.7719" width="2.12617" height="0.5" transform="rotate(149 8.74289 17.7719)" fill="#E1FF00" stroke="#FEBA7D" stroke-width="0.5"/>
<rect x="19.692" y="18.3533" width="5.65271" height="0.5" transform="rotate(43 19.692 18.3533)" fill="#E1FF00" stroke="#FEBA7D" stroke-width="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,18 @@
# Pug Features Demo
This folder is a small Pug demo showing:
- interpolation with `#{name}`
- conditionals with `if` and `case`
- loops with `each`
- includes with `_summary.pug` and `_mixins.pug`
- layout inheritance with `extends ./layout.pug`
- reusable mixins with `+featureCard(feature)` and `+badge(...)`
Compile it with:
```bash
npx pug-cli priv/ui/pug-demo/index.pug --pretty --out priv/ui/pug-demo
```
That writes `priv/ui/pug-demo/index.html`.

View File

@@ -0,0 +1,8 @@
mixin badge(label, tone)
span.badge(class=`badge--${tone}`)= label
mixin featureCard(feature)
li.feature-card
h3 #{feature.name}
p.muted= feature.description
+badge(feature.statusLabel, feature.status)

View File

@@ -0,0 +1,12 @@
section.panel
h2 Included Summary
p
| This section comes from
code _summary.pug
| . It can still read variables declared in
code index.pug
| , including
strong #{name}
| and the feature count:
strong #{features.length}
| .

149
priv/ui/pug-demo/index.html Normal file
View File

@@ -0,0 +1,149 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pug Features Demo</title>
<style>
:root {
color-scheme: light;
--ink: #202124;
--muted: #5f6368;
--line: #dadce0;
--paper: #ffffff;
--soft: #f7f8fa;
--accent: #0b57d0;
--accent-soft: #e8f0fe;
--good: #137333;
--warn: #b06000;
}
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--soft);
color: var(--ink);
font: 15px/1.5 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
main {
width: min(960px, calc(100% - 32px));
margin: 32px auto;
}
header {
margin-bottom: 24px;
}
h1, h2, h3, p { margin-top: 0; }
h1 { font-size: 32px; line-height: 1.1; }
h2 { font-size: 20px; margin-bottom: 12px; }
h3 { font-size: 16px; margin-bottom: 6px; }
.muted { color: var(--muted); }
.panel, .feature-card {
background: var(--paper);
border: 1px solid var(--line);
border-radius: 8px;
}
.panel {
padding: 18px;
margin-bottom: 16px;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 12px;
margin: 0;
padding: 0;
list-style: none;
}
.feature-card {
padding: 14px;
}
.feature-card p { margin-bottom: 10px; }
.badge {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 2px 8px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--accent);
font-size: 12px;
font-weight: 650;
}
.badge--ready {
background: #e6f4ea;
color: var(--good);
}
.badge--practice {
background: #fef7e0;
color: var(--warn);
}
code {
background: #eef0f3;
border-radius: 4px;
padding: 1px 5px;
}
</style>
</head>
<body>
<main>
<header>
<h1>Pug features for Ada Lovelace</h1>
<p class="muted">This page is rendered from <code>index.pug</code> and extends <code>layout.pug</code>.</p>
</header>
<section class="panel">
<h2>Interpolation</h2>
<p>Hello, Ada Lovelace. Your current plan is PRO.</p>
<p>The literal syntax is <code>#{name}</code>, which inserts escaped text into a line.</p>
</section>
<section class="panel">
<h2>Conditionals</h2>
<p>Welcome back, Ada Lovelace.</p>
<p>You are using the pro plan, so all examples are visible.</p>
</section>
<section class="panel">
<h2>Loops and Mixins</h2>
<ul class="feature-grid">
<li class="feature-card">
<h3>Interpolation</h3>
<p class="muted">Drop values into text with #{name} and escaped output.</p><span class="badge badge--ready">ready</span>
</li>
<li class="feature-card">
<h3>Conditionals</h3>
<p class="muted">Render different branches with if, else if, else, and case.</p><span class="badge badge--ready">ready</span>
</li>
<li class="feature-card">
<h3>Loops</h3>
<p class="muted">Repeat markup with each item in collection syntax.</p><span class="badge badge--ready">ready</span>
</li>
<li class="feature-card">
<h3>Includes and extends</h3>
<p class="muted">Compose pages from layouts and smaller partial files.</p><span class="badge badge--practice">practice</span>
</li>
<li class="feature-card">
<h3>Mixins</h3>
<p class="muted">Create reusable snippets that accept arguments.</p><span class="badge badge--ready">ready</span>
</li>
</ul>
</section>
<section class="panel">
<h2>Included Summary</h2>
<p>This section comes from <code>_summary.pug</code>. It can still read variables declared in <code>index.pug</code>, including <strong>Ada Lovelace</strong> and the feature count: <strong>5</strong>.</p>
</section>
</main>
</body>
</html>

View File

@@ -0,0 +1,80 @@
extends ./layout.pug
include ./_mixins.pug
block content
-
const name = "Ada Lovelace";
const signedIn = true;
const plan = "pro";
const features = [
{
name: "Interpolation",
description: "Drop values into text with #{name} and escaped output.",
status: "ready",
statusLabel: "ready"
},
{
name: "Conditionals",
description: "Render different branches with if, else if, else, and case.",
status: "ready",
statusLabel: "ready"
},
{
name: "Loops",
description: "Repeat markup with each item in collection syntax.",
status: "ready",
statusLabel: "ready"
},
{
name: "Includes and extends",
description: "Compose pages from layouts and smaller partial files.",
status: "practice",
statusLabel: "practice"
},
{
name: "Mixins",
description: "Create reusable snippets that accept arguments.",
status: "ready",
statusLabel: "ready"
}
];
header
h1 Pug features for #{name}
p.muted
| This page is rendered from
code index.pug
| and extends
code layout.pug
| .
section.panel
h2 Interpolation
p Hello, #{name}. Your current plan is #{plan.toUpperCase()}.
p
| The literal syntax is
code #{'#{name}'}
| , which inserts escaped text into a line.
section.panel
h2 Conditionals
if signedIn
p Welcome back, #{name}.
else
p Please sign in to see the demo.
case plan
when "free"
p You are using the free plan.
when "pro"
p You are using the pro plan, so all examples are visible.
default
p Your plan is #{plan}.
section.panel
h2 Loops and Mixins
ul.feature-grid
each feature in features
+featureCard(feature)
include ./_summary.pug

102
priv/ui/pug-demo/layout.pug Normal file
View File

@@ -0,0 +1,102 @@
doctype html
html(lang="en")
head
meta(charset="utf-8")
meta(name="viewport", content="width=device-width, initial-scale=1")
title Pug Features Demo
style.
:root {
color-scheme: light;
--ink: #202124;
--muted: #5f6368;
--line: #dadce0;
--paper: #ffffff;
--soft: #f7f8fa;
--accent: #0b57d0;
--accent-soft: #e8f0fe;
--good: #137333;
--warn: #b06000;
}
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--soft);
color: var(--ink);
font: 15px/1.5 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
main {
width: min(960px, calc(100% - 32px));
margin: 32px auto;
}
header {
margin-bottom: 24px;
}
h1, h2, h3, p { margin-top: 0; }
h1 { font-size: 32px; line-height: 1.1; }
h2 { font-size: 20px; margin-bottom: 12px; }
h3 { font-size: 16px; margin-bottom: 6px; }
.muted { color: var(--muted); }
.panel, .feature-card {
background: var(--paper);
border: 1px solid var(--line);
border-radius: 8px;
}
.panel {
padding: 18px;
margin-bottom: 16px;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 12px;
margin: 0;
padding: 0;
list-style: none;
}
.feature-card {
padding: 14px;
}
.feature-card p { margin-bottom: 10px; }
.badge {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 2px 8px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--accent);
font-size: 12px;
font-weight: 650;
}
.badge--ready {
background: #e6f4ea;
color: var(--good);
}
.badge--practice {
background: #fef7e0;
color: var(--warn);
}
code {
background: #eef0f3;
border-radius: 4px;
padding: 1px 5px;
}
body
main
block content