Merge pull request #64 from urbit/dev-summit

Dev summit
This commit is contained in:
Liam Fitzgerald 2024-06-25 14:25:52 -04:00 committed by GitHub
commit 622eee6a66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
359 changed files with 13039 additions and 91590 deletions

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:feaae0eece54db3e92122263706c283674af581d14ffde8a29fb24e1873a35b1
size 6453015
oid sha256:c2ab6607450382e0ec80c7264dad2c72d69672eaf861eb1c24cde5a76921c6a3
size 9972490

View File

@ -3,7 +3,7 @@
flake-utils.url = "github:numtide/flake-utils";
tools = {
flake = false;
url = "github:urbit/tools";
url = "github:urbit/tools/d454e2482c3d4820d37db6d5625a6d40db975864";
};
};

View File

@ -28,17 +28,6 @@ let
--
'';
testThread = dojoCommand:
pkgs.writeTextFile {
name = "${dojoCommand}.hoon";
text = ''
${poke}
=/ m (strand ,vase)
;< [=ship =desk =case] bind:m get-beak
;< ok=? bind:m (poke [ship %dojo] %lens-command !>([%$ [%dojo '${dojoCommand}'] [%stdout ~]]))
(pure:m !>(ok))
'';
};
appThread = generator: app:
pkgs.writeTextFile {
name = ":${app}|${generator}.hoon";
@ -87,11 +76,12 @@ in pkgs.stdenvNoCC.mkDerivation {
sleep 2
${click} -k -p -i ${testThread "-test %/tests ~"} ./pier
${click} -c ./pier "[0 %fyrd [%base %test %noun %noun 0]]"
${click} -k -p -i ${pokeApp "%agents" "noun" "test"} ./pier
${click} -k -p -i ${pokeApp "%generators" "noun" "test"} ./pier
${click} -k -p -i ${pokeApp "%marks" "noun" "test"} ./pier
${click} -k -p -i ${pokeApp "%threads" "noun" "test"} ./pier
${click} -k -p -i ${appThread "mass" "hood"} ./pier
sleep 2
@ -112,7 +102,7 @@ in pkgs.stdenvNoCC.mkDerivation {
'';
checkPhase = ''
if egrep "((FAILED|CRASHED|Failed)|warn:)" $out >/dev/null; then
if egrep "((FAILED|CRASHED|Failed|\[0 %avow 0 %noun 1\])|warn:)" $out >/dev/null; then
exit 1
fi
'';

View File

@ -85,7 +85,7 @@
=^ cards state
?+ mark ~|([%aqua-bad-mark mark] !!)
%aqua-events (poke-aqua-events:ac !<((list aqua-event) vase))
%pill (poke-pill:ac !<(pill vase))
%pill (poke-pill:ac !<(pill vase))
%noun (poke-noun:ac !<(* vase))
%azimuth-action (poke-azimuth-action:ac !<(azimuth-action vase))
==
@ -663,34 +663,55 @@
(pe ~bud) :: XX why ~bud? need an example
::
%read
?~ pier=(~(get by ships.piers) from.ae)
(pe from.ae)
?~ pier=(~(get by ships.piers) ship.from.ae)
(pe ship.from.ae)
=/ cash (~(get by namespace.u.pier) path.ae)
|-
?^ cash
?: (gth num.ae (lent u.cash))
(pe from.ae)
(pe ship.from.ae)
::TODO depends on /ted/aqua/ames behavior in a weird indirect way
=/ for=@p `@`(tail for.ae) ::NOTE moons & comets not supported
=; task=task-arvo
^$(ae [%event for /a/aqua/fine-response task], thus this)
:+ %hear `lane:ames`[%| `@`from.ae]
=/ for=@p `@`(tail lane.for.ae) ::NOTE moons & comets not supported
%- push-events:(pe for)
%- flop =< events
%+ roll u.cash
|= [=yowl:ames i=@ud events=(list unix-event)]
:- +(i)
:_ events
:- /a/aqua/fine-response/[(scot %ud i)]
^- task-arvo
:+ %hear `lane:ames`[%| `@`ship.from.ae]
^- blob:ames
=/ =shot:ames
::NOTE dec is important! so dumb!!
(sift-shot:ames `@`(snag (dec num.ae) u.cash))
::TODO runtime needs to update rcvr field also
::NOTE rcvr life is allowed to be wrong
(etch-shot:ames shot(sndr from.ae, rcvr for))
%- etch-shot:ames
:* [sndr=ship.from.ae rcvr=for]
req=| sam=|
sndr-tick=life.from.ae
rcvr-tick=life.for.ae
origin=~
content=`@ux`yowl
==
::
=/ pacs=(unit (list yowl:ames))
=/ =path [%fine %hunk (scot %ud num.ae) '512' path.ae]
%+ biff
(peek-once:(pe from.ae) %ax %$ [%fine %message path.ae])
(peek-once:(pe ship.from.ae) %ax %$ path)
(soft (list yowl:ames))
?~ pacs (pe from.ae)
?~ pacs (pe ship.from.ae)
=. u.pacs
:: add request to each response packet payload
::
=+ pat=(spat path.ae)
=+ wid=(met 3 pat)
%- flop =< blobs
%+ roll u.pacs
|= [=yowl:ames num=_1 blobs=(list @ux)]
:- +(num)
:_ blobs
(can 3 4^num 2^wid wid^`@`pat (met 3 yowl)^yowl ~)
=. namespace.u.pier
(~(put by namespace.u.pier) path.ae u.pacs)
=. ships.piers
(~(put by ships.piers) from.ae u.pier)
(~(put by ships.piers) ship.from.ae u.pier)
$(cash pacs, thus this)
::
%event

View File

@ -42,8 +42,8 @@
++ on-poke
|= [=mark =vase]
^- (quip card _this)
?> =(our src):bowl
?: ?=(%noun mark)
?> (team:title [our src]:bowl)
=/ code !<((unit @t) vase)
=/ msg=tape
?~ code
@ -55,6 +55,13 @@
"""
%- (slog leaf+msg ~)
[~ this(passcode code)]
?: ?=(%json mark)
=/ jon=json !<(json vase)
=, dejs:format
=/ cmd
((of clear-eyre-cache+(ot url+so ~) ~) jon)
?> ?=(%clear-eyre-cache -.cmd)
[[%pass /cmd %arvo %e %set-response +.cmd ~]~ this]
?. ?=(%handle-http-request mark)
(on-poke:def mark vase)
=+ !<([eyre-id=@ta =inbound-request:eyre] vase)
@ -315,6 +322,19 @@
:~ 'location'^s+(cat 3 (fall site '*') (spat path))
'action'^(render-action:v-eyre action)
==
::
:: /eyre/cache.json
::
[%eyre %cache ~]
%- some
:- %a
%+ turn (sort ~(tap by cache:v-eyre) aor)
|= [url=@t aeon=@ud val=(unit cache-entry:eyre)]
%- pairs
:~ 'url'^s+url
'aeon'^(numb aeon)
'val'^?~(val ~ (render-cache-entry:v-eyre u.val))
==
::
:: /eyre/connections.json
::
@ -566,7 +586,6 @@
%- pairs
:~ 'messages'^(numb (lent messages))
'packets'^(numb ~(wyt in packets))
'heeds'^(set-array heeds from-duct)
'keens'^(set-array ~(key by keens) path)
==
::
@ -630,7 +649,6 @@
:: }, ...],
:: closing: [bone, ..., bone],
:: corked: [bone, ..., bone],
:: heeds: [['/paths', ...] ...]
:: scries:
:: -> { =path
:: keen-state: {
@ -757,8 +775,6 @@
'closing'^(set-array closing numb)
::
'corked'^(set-array corked numb)
::
'heeds'^(set-array heeds from-duct)
::
'scries'^(scries ~(tap by keens))
==
@ -773,7 +789,7 @@
'next'^(numb next)
::
:- 'unsent-messages' :: as byte sizes
(set-array unsent-messages (cork (cury met 3) numb))
(set-array unsent-messages (cork jam (cork (cury met 3) numb)))
::
'unsent-fragments'^(numb (lent unsent-fragments)) :: as lent
::
@ -1038,6 +1054,9 @@
++ bindings
(scry ,(list [=binding =duct =action]) %e %bindings ~)
::
++ cache
(scry ,(map url=@t [aeon=@ud (unit cache-entry)]) %e %cache ~)
::
++ connections
(scry ,(map duct outstanding-connection) %e %connections ~)
::
@ -1065,6 +1084,27 @@
%gen :((cury cat 3) '+' (spat [desk path]:generator.action))
%app (cat 3 ':' app.action)
==
::
++ render-cache-entry
|= cache-entry
^- json
%- pairs:enjs:format
:~ 'auth'^b+auth
'payload'^(render-simple-payload simple-payload.body)
==
::
++ render-simple-payload
|= simple-payload:http
^- json
=, enjs:format
%- pairs
:~ 'status'^(numb status-code.response-header)
'data'^?~(data ~ (numb p.u.data))
::
:+ 'headers' %a
%+ turn headers.response-header
|=([k=@t v=@t] (pairs 'key'^s+k 'value'^s+v ~))
==
--
::
:: helpers

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@ -1,20 +1,16 @@
<!doctype html>
<html>
<head>
<title>Debug Dashboard</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="stylesheet" href="/~debug/css/index.css" />
<link rel="icon" type="image/png" href="/~launch/img/Favicon.png">
</head>
<body class="w-100 h-100">
<div id="root" class="w-100 h-100">
</div>
<script src="/~debug/js/channel.js"></script>
<script src="/~debug/js/session.js"></script>
<script src="/~debug/js/index.js"></script>
</body>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Debug Dashboard</title>
<style type="text/css" src="/src/index.css"></style>
<script type="module" crossorigin src="/~debug/index.js"></script>
<link rel="stylesheet" crossorigin href="/~debug/index.css">
</head>
<body>
<div id="root"></div>
<script src="/~debug/channel.js"></script>
<script src="/~debug/js/session.js"></script>
</body>
</html>

125
pkg/arvo/app/debug/index.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
import{errors as c,isChunkObject as y}from"./util.js";import"./index.js";let n=globalThis.File,p=globalThis.Blob;const m=o=>{n=o},g=o=>{p=o},{INVALID:z,GONE:s,MISMATCH:w,MOD_ERR:E,SYNTAX:l,DISALLOWED:O}=c;class D{constructor(e,i){this.fileHandle=e,this.file=i?e.file:new n([],e.file.name,e.file),this.size=i?e.file.size:0,this.position=0}async write(e){if(!this.fileHandle.file)throw new DOMException(...s);let i=this.file;if(y(e)){if(e.type==="write"){if(typeof e.position=="number"&&e.position>=0&&(this.position=e.position,this.size<e.position&&(this.file=new n([this.file,new ArrayBuffer(e.position-this.size)],this.file.name,this.file))),!("data"in e))throw new DOMException(...l("write requires a data argument"));e=e.data}else if(e.type==="seek")if(Number.isInteger(e.position)&&e.position>=0){if(this.size<e.position)throw new DOMException(...z);this.position=e.position;return}else throw new DOMException(...l("seek requires a position argument"));else if(e.type==="truncate")if(Number.isInteger(e.size)&&e.size>=0){i=e.size<this.size?new n([i.slice(0,e.size)],i.name,i):new n([i,new Uint8Array(e.size-this.size)],i.name,i),this.size=i.size,this.position>i.size&&(this.position=i.size),this.file=i;return}else throw new DOMException(...l("truncate requires a size argument"))}e=new p([e]);let t=this.file;const a=t.slice(0,this.position),d=t.slice(this.position+e.size);let r=this.position-a.size;r<0&&(r=0),t=new n([a,new Uint8Array(r),e,d],t.name),this.size=t.size,this.position+=e.size,this.file=t}async close(){if(!this.fileHandle.file)throw new DOMException(...s);this.fileHandle.file=this.file,this.file=this.position=this.size=null,this.fileHandle.onclose&&this.fileHandle.onclose(this.fileHandle)}}class f{constructor(e="",i=new n([],e),t=!0){this.kind="file",this.deleted=!1,this.file=i,this.name=e,this.writable=t}async getFile(){if(this.deleted||this.file===null)throw new DOMException(...s);return this.file}async createWritable(e){if(!this.writable)throw new DOMException(...O);if(this.deleted)throw new DOMException(...s);return new D(this,!!(e!=null&&e.keepExistingData))}async isSameEntry(e){return this===e}destroy(){this.deleted=!0,this.file=null}}class h{constructor(e,i=!0){this.kind="directory",this.deleted=!1,this._entries={},this.name=e,this.writable=i}async*entries(){if(this.deleted)throw new DOMException(...s);yield*Object.entries(this._entries)}async isSameEntry(e){return this===e}async getDirectoryHandle(e,i={}){if(this.deleted)throw new DOMException(...s);const t=this._entries[e];if(t){if(t instanceof f)throw new DOMException(...w);return t}else{if(i.create)return this._entries[e]=new h(e);throw new DOMException(...s)}}async getFileHandle(e,i={}){const t=this._entries[e];if(t){if(t instanceof f)return t;throw new DOMException(...w)}else{if(i.create)return this._entries[e]=new f(e);throw new DOMException(...s)}}async removeEntry(e,i={}){const t=this._entries[e];if(!t)throw new DOMException(...s);t.destroy(i.recursive),delete this._entries[e]}destroy(e){for(let i of Object.values(this._entries)){if(!e)throw new DOMException(...E);i.destroy(e)}this._entries={},this.deleted=!0}}const M=new h(""),u=()=>M;export{f as FileHandle,h as FolderHandle,u as default,g as setBlobImpl,m as setFileImpl};

View File

@ -0,0 +1,7 @@
function __vite__mapDeps(indexes) {
if (!__vite__mapDeps.viteFileDeps) {
__vite__mapDeps.viteFileDeps = ["memory.js","index.js","index.css"]
}
return indexes.map((i) => __vite__mapDeps.viteFileDeps[i])
}
import{_ as l}from"./index.js";const E={INVALID:["seeking position failed.","InvalidStateError"],GONE:["A requested file or directory could not be found at the time an operation was processed.","NotFoundError"],MISMATCH:["The path supplied exists, but was not an entry of requested type.","TypeMismatchError"],MOD_ERR:["The object can not be modified in this way.","InvalidModificationError"],SYNTAX:e=>[`Failed to execute 'write' on 'UnderlyingSinkBase': Invalid params passed. ${e}`,"SyntaxError"],ABORT:["The operation was aborted","AbortError"],SECURITY:["It was determined that certain files are unsafe for access within a Web application, or that too many calls are being made on file resources.","SecurityError"],DISALLOWED:["The request is not allowed by the user agent or the platform in the current context.","NotAllowedError"]},y=e=>typeof e=="object"&&typeof e.type<"u";async function v(e){var o,r,a;const{FolderHandle:t,FileHandle:u}=await l(()=>import("./memory.js"),__vite__mapDeps([0,1,2])),{FileSystemDirectoryHandle:m}=await l(()=>import("./index.js").then(n=>n.a),__vite__mapDeps([1,2])),p=(r=(o=e[0].webkitRelativePath)===null||o===void 0?void 0:o.split("/",1)[0])!==null&&r!==void 0?r:"",_=new t(p,!1);for(let n=0;n<e.length;n++){const i=e[n],d=!((a=i.webkitRelativePath)===null||a===void 0)&&a.length?i.webkitRelativePath.split("/"):["",i.name];d.shift();const f=d.pop(),w=d.reduce((c,s)=>(c._entries[s]||(c._entries[s]=new t(s,!1)),c._entries[s]),_);w._entries[f]=new u(i.name,i,!1)}return new m(_)}async function b(e){const{FileHandle:o}=await l(()=>import("./memory.js"),__vite__mapDeps([0,1,2])),{FileSystemFileHandle:r}=await l(()=>import("./index.js").then(t=>t.F),__vite__mapDeps([1,2]));return Array.from(e).map(t=>new r(new o(t.name,t,!1)))}export{E as errors,y as isChunkObject,v as makeDirHandleFromFileList,b as makeFileHandlesFromFileList};

View File

@ -168,7 +168,7 @@
::
;~ pfix tis
;~ pose
(parse-variable (jest %dir) ;~(pfix ace :(stag 0 %ex parse-rood)))
(parse-variable (cold %dir (jest 'dir ')) :(stag 0 %ex parse-rood))
(parse-variable sym ;~(pfix ace parse-source))
==
==

View File

@ -5,7 +5,6 @@
:: keep relevant mark conversions in cache for performance
::
/$ blit-to-json %blit %json
/$ json-to-blit %json %blit
/$ json-to-task %json %herm-task
::
=, jael

View File

@ -2,8 +2,8 @@
/+ drum=hood-drum, helm=hood-helm, kiln=hood-kiln
|%
+$ state
$~ [%26 *state:drum *state:helm *state:kiln]
$>(%26 any-state)
$~ [%27 *state:drum *state:helm *state:kiln]
$>(%27 any-state)
::
+$ any-state
$% [ver=?(%1 %2 %3 %4 %5 %6) lac=(map @tas fin-any-state)]
@ -27,6 +27,7 @@
[%24 drum=state-4:drum helm=state-2:helm kiln=state-10:kiln]
[%25 drum=state-5:drum helm=state-2:helm kiln=state-10:kiln]
[%26 drum=state-6:drum helm=state-2:helm kiln=state-10:kiln]
[%27 drum=state-6:drum helm=state-2:helm kiln=state-11:kiln]
==
+$ any-state-tuple
$: drum=any-state:drum

View File

@ -26,7 +26,7 @@
+$ card $+(card card:agent:gall)
+$ state-0
$+ state-0
$: =loam:dirt:neo :: layer 1
$: =loam:dirt:neo :: layer 1
=farm:neo :: layer 2
::
=town:neo :: subscription
@ -75,7 +75,7 @@
=| state-0
=* state -
=<
%- mute
!.
%+ libverb |
%- agent:dbug
^- agent:gall
@ -83,7 +83,7 @@
+* this .
run ~(. +> [bowl ~])
def ~(. (default-agent this %|) bowl)
++ on-init
++ on-init
^- (quip card _this)
=^ cards state
abet:boot:run
@ -131,7 +131,8 @@
[cards this]
++ on-peek on-peek:run
--
:: %- mute
:: %- mute
!.
|_ [=bowl:gall cards=(list card)]
:: |aux: auxilliary helpers
+| %aux
@ -150,7 +151,7 @@
:- p/our.bowl
~[n/~ %sys]
:: |do: effect creation
+| %do
+| %do
++ do-watch
|= [=wire =dock =path]
(pass wire %agent dock watch/path)
@ -197,6 +198,7 @@
++ do-ack
|= =ack:neo
^- (list card)
:: %- (slog leaf/"do from: {<p.p.ack>} to: {<q.p.ack>}" ~)
?: =(p.ack sys-pith)
%. *(list card)
?~ q.ack
@ -228,23 +230,22 @@
=/ =name:neo (de-pith:name:neo pith)
=/ nonce (scot %uv run-nonce:(~(got by mate) ship.name))
=/ =spar:ames [ship.name [nonce (pout pith)]]
~& fetching/spar
!! :: (pass wire %keen spar)
++ do-gall-grow
|= [=pith:neo sag=(unit saga:neo)]
^- card
=/ =wire gall-grow/(pout pith)
=/ =page
=/ =page
?~ sag none/~
neo-feat/(saga:soften u.sag)
(pass wire %grow (pout pith) page)
::
:: ?: =(p.flow
:: ?: =(p.flow
:: |on: event handlers
+| %on
::
++ on-poke
|= [=mark =vase]
|= [=mark =vase]
^+ run
?+ mark ~|(bad-poke-mark/mark !!)
%neo-move =;(f (f !<(_+<.f vase)) on-move)
@ -272,7 +273,7 @@
++ on-move
|= =move:neo
^+ run
%- (slog leaf/"{(en-tape:pith:neo p.move)} -> {(en-tape:pith:neo p.q.move)}: {<-.q.q.move>}" ~)
:: %- (slog leaf/"{(en-tape:pith:neo p.move)} -> {(en-tape:pith:neo p.q.move)}: {<-.q.q.move>}" ~)
=/ src=name:neo (de-pith:name:neo p.move)
=/ dst=name:neo (de-pith:name:neo p.q.move)
?> =(src.bowl ship.src)
@ -282,7 +283,7 @@
++ on-ack
|= =ack:neo
=/ dst=name:neo (de-pith:name:neo p.p.ack)
?> =(src.bowl ship.dst)
:: ?> =(src.bowl ship.dst)
?: =(sys-pith p.p.ack)
%. run
?~ q.ack
@ -291,6 +292,7 @@
%gone (slog leaf/"Missing dep: {<term.u.q.ack>}" ~)
%goof (slog leaf/"nacked on flow {<p.ack>}" tang.u.q.ack)
==
:: %- (slog leaf/"on-ack from: {<p.p.ack>} to: {<q.p.ack>}" ~)
(on-move q.p.ack p.p.ack %poke ack/!>(q.ack))
::
++ on-dirt-card
@ -328,6 +330,8 @@
=/ sag (need (peek-x:till pith))
=/ =feat:neo
?~ sag [*aeon:neo sig/~]
?: =(%vase p.q.u.sag)
[*aeon:neo sig/~]
(saga:soften u.sag)
=. run (emit %give %fact ~ neo-feat+!>(feat))
(emit %give %kick ~ ~)
@ -396,7 +400,7 @@
|= =poem:neo
neo-poem+!>(poem)
::
++ tell
++ tell
%- raise
|= =myth:neo
neo-myth+!>(myth)
@ -417,12 +421,11 @@
^+ run
=/ [gis=(list gift:dirt:neo) lom=loam:dirt:neo fam=farm:neo]
(tell:till epic)
=. loam lom
=. loam lom
=. farm fam
=. run (lazarus gis)
=. run (take:rage gis)
=. run (collect-rent gis)
~& gifs/gis
run
::
++ plow ~(. plow:aux loam)
@ -482,7 +485,7 @@
=. mart.u.mal (~(put in mart.u.mal) [care src])
=. town (~(put of:neo town) pith u.mal)
sale
:: XX: search upwards for
:: XX: search upwards for
=| =mall:neo
=. mart.mall (~(put in mart.mall) [care src])
?. =(~ find-deli)
@ -522,13 +525,12 @@
++ take-fetch
|= syn=sign-arvo
^+ sale
~& got-fetch/pith
?> ?=([%ames %tune *] syn)
?~ roar.syn
~& missing-roar/pith
sale
=/ [=path dat=(unit page)] dat.u.roar.syn
?~ dat
?~ dat
~& missing-page/pith
sale
%- on-saga
@ -543,16 +545,14 @@
mismatch-saga-sale/[exe.p.u.shop.mall exe.p.p.res]
~
=. sale (put-mall mall)
=/ del
=/ del
~| town/town
~| mall/mall
~| pith/pith
(need find-deli)
~& del/del
=/ kid (dif:pith:neo pith del)
~& kid/kid
abet:(fetched:~(meat sale del) (dif:pith:neo pith del) res)
:: XX: possibly check that
:: XX: possibly check that
++ find-deli
=| res=(unit pith:neo)
=/ at=pith:neo pith
@ -577,18 +577,26 @@
++ meat .
++ new
|= =yuga:neo
=. yuga.deli yuga
~& new-yuga/yuga
=. yuga.deli
%- gas-yuga
%+ murn ~(tap by ~(tar of:neo yuga))
|= [=pith:neo =aeon:neo]
=/ res (look %x pith)
?: ?=(?(~ [~ ~]) res)
`[pith aeon]
?~ rot=(~(get of:neo u.u.res) ~)
`[pith aeon]
?: =(p.exe.p.p.u.rot p.exe.p.aeon)
~
`[pith aeon]
meat
++ fetched
|= [kid=pith:neo =saga:neo]
=. yuga.deli (~(del of:neo yuga.deli) kid)
=. epic.deli (~(put of:neo epic.deli) kid saga)
~& fetched/deli
?. =(~ ~(tap of:neo yuga.deli))
meat
=/ =epic:neo epic.deli
~& finalizing/[pith epic]
=. epic.deli *epic:neo
=. run (tell pith (~(rep of:neo *epic:neo) pith epic))
meat
@ -613,7 +621,6 @@
::
++ on-sync-sign
|= =sign:agent:gall
~& town/town
^+ sale
?+ -.sign ~|(bad-sign/-.sign !!)
%watch-ack
@ -624,7 +631,6 @@
::
%fact
?. =(%neo-yuga p.cage.sign)
~& weird-mall-fact/p.cage.sign
sale
(on-yuga !<(yuga:neo q.cage.sign))
::
@ -661,9 +667,8 @@
^+ sale
:: =. sale abet:(new:meat yuga)
=/ lis ~(tap of:neo yuga)
|-
|-
?~ lis
~& done-yuga-town/town
abet:(new:meat yuga)
=/ [kid=pith:neo =aeon:neo] i.lis
=/ pit (welp pith kid)
@ -674,12 +679,9 @@
~& nothing/pit
$(lis t.lis)
?~ u.res
~& dead/pit
:: XX: what means??
$(lis t.lis)
~& alive/pit
?: =(p.u.u.res aeon)
~& clone/pit
$(lis t.lis, yuga (~(del of:neo yuga) kid))
~& fresh/pit
=. run abet:(~(fresh sale pit) aeon)
@ -689,7 +691,7 @@
=/ wir (wire %fetch)
=. run (emit (do-watch-her wir get-ship fetch-path))
sale
++ watch-sync
++ watch-sync
=/ wir (wire %sync)
=. run (emit (do-watch-her (wire %sync) get-ship peer-path))
sale
@ -718,6 +720,7 @@
++ abet run
++ rent .
++ get-ward (~(gut of:neo city) pith *ward:neo)
++ has-kid !=(~ kid:(~(dip of:neo city) pith))
++ put-ward |=(=ward:neo rent(city (~(put of:neo city) pith ward)))
++ fact
|= [=care:neo paxs=(set pith:neo)]
@ -738,7 +741,6 @@
^- yuga:neo
?~ pic=(need (look care p/our.bowl pith))
*yuga:neo
~& epic/u.pic
(epic-to-yuga u.pic)
::
++ stop
@ -774,7 +776,7 @@
?: =(until pith)
%self
=/ left (dif:pith:neo pith until)
?: (~(has by (~(kid of:neo tide) pith)) left)
?: (~(has by (~(kid of:neo tide) pith)) left)
%par
%anc
::
@ -791,10 +793,12 @@
=. rent (fact %z zed.war)
?~ nex=(dif:pith:neo pith until)
rent
?. has-kid
rent
$(pith (snoc pith i.nex))
--
++ rage
|%
|%
++ stalk
|= [=hunt:neo =howl:neo]
^+ run
@ -802,6 +806,50 @@
=. rav (fume-add rav care.hunt howl)
=. riot (~(put of:neo riot) pith.hunt rav)
run
++ heal
|= [dead=hunt:neo how=(set howl:neo)]
^+ run
=/ how ~(tap in how)
|-
?~ how
run
=/ =howl:neo i.how
?. ?=(%rely -.howl) :: XX: handle %halt %sell
$(how t.how)
=/ [=term =pith:neo] +.howl
=/ =move:neo
[pith.dead [p/our.bowl pith] %poke %dead !>(term)]
=. run
abet:(arvo move)
$(how t.how)
++ ease
|= [=pith:neo how=(set howl:neo)]
%- ~(gas in *(set howl:neo))
%+ skip ~(tap in how)
|= =howl:neo
?. ?=(%rely -.howl)
|
=(pith pith.howl)
++ cure
|= dead=pith:neo
=. riot
%- gas-riot
%+ turn ~(tap by ~(tar of:neo riot))
|= [=pith:neo =rave:neo]
=. exe.rave (ease dead exe.rave)
=. why.rave (ease dead why.rave)
=. zed.rave (ease dead zed.rave)
[pith rave]
run
++ reap
|= [change=pith:neo =loot:neo]
=/ =name:neo (de-pith:name:neo change)
=? run =(our.bowl ship.name)
(cure pith.name)
=/ =rave:neo (~(gut of:neo riot) change *rave:neo)
=. run (heal:rage:(heal:rage:(heal x/change exe.rave) y/change why.rave) z/change zed.rave)
run(riot (~(del of:neo riot) change))
::
++ fury
|= gis=(list gift:dirt:neo)
%- gas-leaf
@ -825,7 +873,8 @@
=/ =rave:neo (~(gut of:neo riot) here *rave:neo)
=? run =(here change)
(spaz exe.rave %x change)
=? run =(here (~(parent of:neo tide) change))
=/ par (~(parent of:neo loam) change)
=? run =(`here par)
(spaz why.rave %y change)
=. run
(spaz zed.rave %z change)
@ -842,6 +891,8 @@
run
=/ [=pith:neo =loot:neo] i.gis
=. run (sweep i.gis)
=? run =(%del mode.loot)
(reap pith loot)
$(gis t.gis)
::
++ fume-add
@ -867,7 +918,7 @@
^+ run
:: XX: weird shadowing, be careful
=/ =rave:neo (~(gut of:neo riot) pith.hunt *rave:neo)
=. rave
=. rave
(fume-del rave care.hunt halt/~)
=. riot (~(put of:neo riot) pith.hunt rave)
(resolved:stop hunt)
@ -883,7 +934,7 @@
[pith.from [p/our.bowl pith] %poke %rely !>(rely)]
abet:(arvo move)
--
::
::
++ lazarus
|= git=grit:neo
^+ run
@ -896,8 +947,8 @@
=/ res (need (look-x:till case.loot pith))
?: &(?=(^ res) =(%vase p.q.u.res))
$(git t.git)
=. run
(emit (do-gall-grow pith (need (look-x:till case.loot pith))))
:: =. run
:: (emit (do-gall-grow pith (need (look-x:till case.loot pith))))
$(git t.git)
++ take-dirt-card
@ -951,7 +1002,6 @@
run
++ resolved
|= =hunt:neo
~& resolved/hunt
=/ fow=(unit flow:neo) (~(get by by-hunt.halt) hunt)
?~ fow
run
@ -963,7 +1013,7 @@
run
=/ q (~(got by clog.halt) u.fow)
|-
?: =(~ q)
?: =(~ q)
=. clog.halt (~(del by clog.halt) u.fow)
run
=^ nex=move:neo q ~(get to q)
@ -984,7 +1034,7 @@
::
++ pith
^- pith:neo
:- p/our.bowl
:- p/our.bowl
(~(pith press imp/stud) %out)
++ vase
^- ^vase
@ -1060,12 +1110,12 @@
!, *hoon
|= [to=stud:neo in=in]
^- vase
=/ =stud:neo
=/ =stud:neo
~| missing-con/[grab to]
(~(got by con.dive) [grab %$ to])
=/ conv ~(do con stud)
(slym run:conv in)
::
::
++ all-grow
|= grow=stud:neo
^- vase :: of $-(pail grow-type)
@ -1084,17 +1134,17 @@
^- out
~! p.pail
~! grow
=/ =stud:neo
=/ =stud:neo
~| missing-con/[p.pail grow]
(~(got by con.dive) [p.pail %$ grow])
=/ conv ~(do con stud)
!<(out (slam run:conv q.pail))
::
::
::
++ con
|_ =stud:neo
++ do
=/ vax=vase
++ do
=/ vax=vase
q.q:(need fil:(need (need (~(peek till:aux [loam farm]) %x [p/our.bowl pith]))))
~| con-pith/pith
|%
@ -1121,7 +1171,7 @@
|. ^- ?
=/ src=vase ~(get pro grab)
=/ dst=vase ~(get pro grow)
=/ need=type
=/ need=type
=< p
%+ slap (with-faces:ford:neo get-reef src/src dst/dst ~)
!,(*hoon *$-(src dst))
@ -1143,7 +1193,7 @@
!=(~ (~(peek plow:aux loam) p/our.bowl pith))
++ pith (~(pith press pro/stud) %out)
++ exists (exists-file (~(path press pro/stud) %src))
--
--
::
++ press
|_ =post:neo
@ -1153,7 +1203,7 @@
|= =pith:neo
^- [kind:ford:neo post:neo pith:neo]
~| ejecting/pith
=^ =disk:neo pith
=^ =disk:neo pith
?> ?=([%cod *] pith)
(eject:floppy t.pith)
?> ?=([kind:ford:neo tack:neo @ *] pith)
@ -1161,7 +1211,7 @@
=/ =tack:neo i.t.pith
:+ kind [tack ?@(disk i.t.t.pith [i.t.t.pith ship.disk term.disk])]
t.t.t.pith
++ slip
|= [=kind:ford:neo pax=pith:neo]
=/ [@ p=post:neo =pith:neo]
@ -1230,9 +1280,9 @@
++ finalize
=. ripe &
=/ base=pith:neo /cod/std/out/con
=/ cons
=/ cons
~(tap by ~(tar of:neo ~(snip of:neo (~(dip of:neo tide) base))))
|-
|-
?~ cons
=. run gen-grab
gen-grow
@ -1246,7 +1296,7 @@
++ gen-grab
=/ grabs ~(tap in ~(key by by-grab.dive))
~& genning/grabs
|-
|-
?~ grabs
run
=/ =vase (all-grab i.grabs)
@ -1256,7 +1306,7 @@
++ gen-grow
=/ grows ~(tap in ~(key by by-grow.dive))
~& genning-grows/grows
|-
|-
?~ grows
run
=/ =vase (all-grow i.grows)
@ -1272,7 +1322,6 @@
!=(txt q.q.u.pal)
++ read-txt
|= pax=path
~& reading-txt/pax
=+ .^(src=@t %cx `path`(welp root pax))
=. pax (snip pax)
=. run (write-txt pax src)
@ -1282,7 +1331,6 @@
++ read-file
|= pax=path
^+ run
~& reading/pax
?. =((rear pax) %hoon)
(read-txt pax)
=+ .^(src=@t %cx `path`(welp root pax))
@ -1298,7 +1346,6 @@
(mean p.res)
%- mule |.
(scan (trip src) (rein:ford:neo [our.bowl (pave:neo (snip pax))]))
~& [lib=lib pro=pro]:file
=/ has-imports=?
?& (levy pro.file |=(pro:ford:neo ~(exists pro stud)))
(levy lib.file |=(lib:ford:neo ~(exists lib stud)))
@ -1321,7 +1368,7 @@
~| ~(key by ~(tar of:neo loam))
~| imports/file(hoon *hoon)
?> built-imports
=^ pre=pith run
=^ pre=pith run
(make-prelude (snip pax) file)
=/ =conf:neo
(~(gas by *conf:neo) [%sut (ours pre)] ~)
@ -1333,7 +1380,7 @@
^+ run
?~ pos
run
=/ pat
=/ pat
(~(path press fil/i.pos) %src)
?: ~(built pro i.pos)
$(pos t.pos)
@ -1348,7 +1395,7 @@
^+ run
?~ pos
run
=/ pat
=/ pat
(~(path press pro/i.pos) %src)
?: ~(built pro i.pos)
$(pos t.pos)
@ -1360,7 +1407,7 @@
^+ run
?~ pos
run
=/ pat
=/ pat
(welp #/cod/grab (stud-to-pith:neo i.pos))
?: !=(~ (~(peek plow:aux loam) p/our.bowl pat))
$(pos t.pos)
@ -1372,7 +1419,7 @@
^+ run
?~ pos
run
=/ pat
=/ pat
(welp #/cod/grow (stud-to-pith:neo i.pos))
?: !=(~ (~(peek plow:aux loam) p/our.bowl pat))
$(pos t.pos)
@ -1388,7 +1435,7 @@
^+ run
?~ lis
run
=/ pat
=/ pat
(~(path press lib/i.lis) %src)
?: ~(built lib i.lis)
$(lis t.lis)
@ -1465,7 +1512,7 @@
++ make-prelude
|= [pax=pith =file:ford:neo]
^- [pith _run]
=/ pre-path=pith
=/ pre-path=pith
(slip:press %pre pax)
[pre-path (make-deps pre-path (file-to-deps file))]
++ write-hoon
@ -1508,11 +1555,13 @@
=. run copy-clay
:: =. run (emit %pass /bind-site %arvo %e %connect [~ dap.bowl ~] dap.bowl)
=. run (emit do-std-warp)
=. run
(emit (do-card #/[p/our.bowl]/sky %make %sky `sky/!>([%system [~[%home] ~] 1]) ~))
=. run
=. run
(emit (do-card #/[p/our.bowl] %make %root ~ ~))
=. run
(emit (do-card #/[p/our.bowl]/sky %make %sky ~ ~))
=. run
(emit (do-card #/[p/our.bowl]/srv/hawk %make %hawk-eyre ~ ~))
=. run
=. run
(emit (do-card #/[p/our.bowl]/srv/sky %make %sky-eyre ~ ~))
run
++ pess |=(=post:neo (~(pith press post) %out))
@ -1543,7 +1592,7 @@
^- form:neo
|_ [=bowl:neo =aeon:neo =pail:neo]
++ poke
|= pok=pail:neo
|= pok=pail:neo
^- (quip card:neo pail:neo)
`pail
::
@ -1623,7 +1672,7 @@
::
:: +arvo: local callstack
++ arvo
=+ verb=&
=+ verb=|
=/ old state
:: data for blocking semantics
=| =block:neo
@ -1651,10 +1700,10 @@
|%
++ can-ack
^- ?
!?=([%poke %ack *] q.q.init-move)
!?=([%poke ?(%ack %rely %dead) *] q.q.init-move)
++ abet
^+ run
?: =([~ ~] block)
?: =([~ ~] block)
=? run can-ack
(emil `(list card)`(do-ack [p p.q]:init-move err.block))
=. run (emil (turn up do-move))
@ -1714,9 +1763,17 @@
::
++ trace-card
|= =move:neo
^- tank
::?: =((snag 1 p.q.move) %srv)
:: same
%- trace
^- tang
:_ ~
:- %leaf
"{(en-tape:pith:neo p.move)} -> {(en-tape:pith:neo p.q.move)}: {<-.q.q.move>}"
%+ welp
"{(en-tape:pith:neo p.move)} -> {(en-tape:pith:neo p.q.move)}: "
?+ -.q.q.move (trip -.q.q.move)
%poke "%poke {<p.pail.q.q.move>}"
==
++ trace
|= =tang
?. verb same
@ -1752,7 +1809,7 @@
$(arvo new-arvo, done (snoc done nex))
++ poke
|= =pail:neo
^+ arvo ::
^+ arvo ::
=^ cards=(list card:neo) arvo
(soft-surf |.(su-abet:(su-poke:surf pail)))
(ingest cards)
@ -1760,7 +1817,7 @@
:: XX: a hack
::
:: this is implicity recursive, and all external dependencies of
:: the children need to be woken up. this also breaks referential
:: the children need to be woken up. this also breaks referential
:: transparency
++ tomb
|= *
@ -1770,6 +1827,7 @@
++ apply
|= =move:neo
^+ arvo
%- (trace-card move)
?. =(~ err.block)
:: skip if we have errored
arvo
@ -1777,7 +1835,6 @@
=. src (de-pith:name:neo p.move)
=/ =name:neo (de-pith:name:neo p.q.move)
=. here +:p.q.move
%- (trace leaf/"{<-.q.q.move>} {(spud (pout here))}" ~)
?- -.q.q.move
%make (make +.q.q:move)
%poke (poke +.q.q:move)
@ -1811,28 +1868,47 @@
work
::
++ jazz
=| bad=(set term)
|= [=conf:neo =deps:neo]
^- [bad=(set term) block=(set tour:neo)]
%+ roll ~(tap by deps)
|= [[=term required=? =quay:neo] bad=(set term) block=(set hunt:neo)]
^+ [bad arvo]
=/ deps ~(tap by deps)
|- ^+ [bad arvo]
?~ deps
[bad arvo]
=/ [=term =deed:neo =quay:neo] i.deps
=/ req=?
?@(deed deed req.deed)
=/ timeout=(unit @dr)
?@(deed ~ time.deed)
=/ =care:neo (get-care:quay:neo quay)
?: &(required !(~(has by conf) term))
:_(block (~(put in bad) term))
?: &(!required !(~(has by conf) term))
[bad block]
?: &(req !(~(has by conf) term))
=. bad (~(put in bad) term)
$(deps t.deps)
?: &(!req !(~(has by conf) term))
$(deps t.deps)
=/ pit=pith:neo (~(got by conf) term)
=/ res (look care pit)
=/ nam=name:neo (de-pith:name:neo pit)
?~ res
?~ res
?: =(our.bowl ship.nam)
?. required
[bad block]
:_(block (~(put in bad) term))
[bad (~(put in block) care pit)]
?~ u.res
:_(block (~(put in bad) term))
[bad block] ::
::
?. req
$(deps t.deps)
=. bad (~(put in bad) term)
$(deps t.deps)
?: &(=(timeout `0) req)
=. bad (~(put in bad) term)
$(deps t.deps)
=? get.block req
(~(put in get.block) care pit)
=. run abet:(~(start sale pit) [p/our.bowl here] care)
=? run !req
(stalk:rage care^pit %rely term here)
$(deps t.deps)
?^ u.res
$(deps t.deps)
=. bad (~(put in bad) term)
$(deps t.deps)
::
++ dance
|= [=crew:neo =band:neo]
^+ arvo
@ -1841,11 +1917,13 @@
?~ cew
arvo
=/ [=term =pith:neo] i.cew
=/ d=(unit [req=? =quay:neo]) (~(get by band) term)
=/ d=(unit [=deed:neo =quay:neo]) (~(get by band) term)
:: skip extraneous, XX: is correct?
?~ d
?~ d
$(cew t.cew)
=/ [req=? =quay:neo] u.d
=/ [=deed:neo =quay:neo] u.d
=/ req=?
?@(deed deed req.deed)
=/ =hunt:neo [(get-care:quay:neo quay) pith]
=/ =name:neo (de-pith:name:neo pith)
?: &(req =(~ (moor quay name)))
@ -1873,9 +1951,9 @@
::
++ make
|= [src=stud:neo init=(unit pail:neo) =crew:neo]
=/ =wave:neo [src ~(dock husk src) crew]
=/ =wave:neo [src ~(dock husk src) crew &]
=. tide (~(put of:neo tide) here wave)
=^ bad=(set term) get.block
=^ bad=(set term) arvo
(jazz crew deps:~(kook husk src))
?. =(~ get.block)
arvo
@ -1913,19 +1991,22 @@
=| cards=(list card:neo)
=/ =kook:neo ~(kook husk code.wave)
=. dock.wave ~(dock husk code.wave)
?. live.wave
~| dead-wave/here
!!
|%
++ su-core .
++ su-emil |=(caz=(list card:neo) su-core(cards (welp cards caz)))
++ su-bowl
++ su-bowl
=/ hare [p/our.bowl here]
^- bowl:neo
:* src
our.bowl
hare
hare
our.bowl
hare
hare
now.bowl
eny.bowl
su-deps
su-deps
su-kids
==
++ su-icon
@ -1950,18 +2031,19 @@
%- ~(gas by *(map term [pith lore:neo]))
^- (list [term pith lore:neo])
%+ murn ~(tap by deps:kook)
|= [=term required=? =quay:neo]
|= [=term =deed:neo =quay:neo]
^- (unit [^term pith:neo lore:neo])
=/ dep=(unit pith) (~(get by crew.wave) term)
=/ req ?@(deed deed req.deed)
?~ dep
~| invariant-missing-required-conf/term
?< required
?< req
~
=/ =name:neo (de-pith:name:neo u.dep)
=/ =care:neo (get-care:quay:neo quay)
?~ lor=(moor quay name)
?< required
~
?< req
`[term u.dep *lore:neo]
:: %- (slog term (epic:dbug:neo epic) ~)
`[term u.dep u.lor]
::
@ -1986,13 +2068,20 @@
++ su-give
|= =gift:neo
?. (~(has in poke.dock.wave) %gift)
~& skipping-give/here
:: ~& skipping-give/here
su-core
(su-poke gift/!>(gift))
::
++ su-poke
|= =pail:neo
^+ su-core
=? live.wave =(p.pail %dead)
=+ !<(dead=term q.pail)
?~ dep=(~(get by deps:kook) dead)
&
=/ [=deed:neo =quay:neo] u.dep
?@ deed !deed
!req.deed
?. (~(has in poke.dock.wave) p.pail)
?: ?=(%ack p.pail)
%. su-core
@ -2000,6 +2089,8 @@
?~ ack
same
(slog (print-quit:neo u.ack))
?: |(=(%dead p.pail) =(%rely p.pail))
su-core
(mean leaf/"no support for {<p.pail>}" ~)
=/ [caz=(list card:neo) new=pail:neo]
(poke:su-form pail)
@ -2091,9 +2182,7 @@
^- $-(vase vase)
?~ as.peer |=(=vase vase)
.^(tube:clay %cc (welp /(scot %p our.bowl)/[r.p.u.p.syn]/(scot %da now.bowl) /(rear kid)/[u.as.peer]))
~& res/~(key by ~(tar of res))
=. res (~(dip of res) path.peer)
~& res/~(key by ~(tar of res))
=/ =note:neo [%poke %clay-res !>(`res:clay:neo`[hand case res])]
~& sending-to/src
=/ =move:neo [[p/our.bowl #/$/clay] src note]
@ -2120,7 +2209,7 @@
++ on-start-peek
|= [src=pith:neo freq=@dr]
^+ run
=/ =peek:gall:neo
=/ =peek:gall:neo
(~(gut by peek.gall.unix) pith [~ ~h24])
=. refresh.peek (min freq refresh.peek)
=/ new=? =(~ src.peek)
@ -2133,7 +2222,7 @@
++ on-stop-peek
|= src=pith:neo
^+ run
=/ =peek:gall:neo
=/ =peek:gall:neo
(~(gut by peek.gall.unix) pith [~ ~h24])
=. src.peek (~(del in src.peek) src)
=. peek.gall.unix
@ -2150,7 +2239,7 @@
++ on-read-peek
=/ =road:neo pith
?> ?=([dude=@ rest=*] road)
=/ pax
=/ pax
%+ welp /(scot %p our.bowl)/[dude.road]/(scot %da now.bowl)
(pout rest.road)
=/ =pail:neo noun/!>(.^(* %gx pax))
@ -2206,7 +2295,7 @@
?> ?=([%behn %wake *] syn)
=/ =time (slav %da da.wir)
=/ timers ~(tap in (~(get ju behn.unix) time))
|-
|-
?~ timers
=. behn.unix (~(del by behn.unix) time)
run
@ -2331,7 +2420,7 @@
=/ rol=stud:neo
(fall role %$)
?~ can=(~(get by con.dive) [p.q.have rol want])
~& missing-can/[p.q.have rol want]
:: ~& missing-can/[p.q.have rol want]
~
=/ conv run:~(do con u.can)
~| dead-horse/[p.q.have rol want u.can]
@ -2348,7 +2437,7 @@
=* loop $
=/ =stud:neo p.q.have
?- -.want
%pro
%pro
(puff p.want rol have)
::
%rol
@ -2384,7 +2473,7 @@
::
::?. (~(has by con.fiesta) [p.have want])
:: ~
::
::
::=/ conv run:~(do con (~(got by con.fiesta) [p.have want]))
::`[want (slam conv q.have)]
::
@ -2432,7 +2521,16 @@
leaf
=. leaf (~(put of:neo leaf) i.lst)
$(lst t.lst)
::
++ gas-riot
=| =riot:neo
|= lst=(list [pith:neo rave:neo])
^+ riot
?~ lst
riot
=. riot (~(put of:neo riot) i.lst)
$(lst t.lst)
::
++ gas-epic
=| =epic:neo
@ -2533,13 +2631,22 @@
=/ lom (~(dip of:neo loam) prefix)
=/ fam (~(dip of:neo farm) prefix)
=/ rav (~(dip of:neo riot) prefix)
=/ ton (~(dip of:neo town) prefix)
=/ cit (~(dip of:neo city) (tail prefix))
:- >fam<
:- >rav<
:- >ton<
:- >cit<
%- zing
%+ turn ~(tap by ~(tar of:neo lom))
|= [=pith:neo =soil:neo]
:~ >pith<
>~(key by soil)<
?~ val=(ram:on:soil:neo soil)
leaf/"No data"
?~ q.val.u.val
leaf/"~"
(sell q.u.q.val.u.val)
==
::
++ print-dbug
@ -2562,4 +2669,3 @@
leaf/"{<p.pail>}"
==
--

View File

@ -20,257 +20,42 @@
::
++ nat-timeout ~s25
::
:: How often to check our IP when we know we're not behind a NAT.
::
++ ip-timeout ~m5
::
:: Chosen because it's run by Cloudflare, and others I tried were
:: inconsistently slow.
::
++ ip-reflector 'https://icanhazip.com'
::
+$ card card:agent:gall
+$ ship-state
$% [%idle ~]
[%poking ~]
[%http until=@da]
[%waiting until=@da]
==
+$ state-1
$: %1
ships=(set ship)
nonce=@ud
$= plan
$~ [%nat ~]
$% [%nat ~]
[%pub ip=(unit @t)]
==
::
+$ state-3
$: %3
mode=?(%formal %informal)
pokes=@ud
timer=(unit [=wire date=@da])
galaxy=@p
==
--
::
%- agent:dbug
::
=| state=state-1
=| state=state-3
=> |%
:: Bind for the the writer monad on (quip effect state)
::
++ rind
|* [effect=mold state=*]
|* state-type=mold
|= $: m-b=(quip effect state-type)
fun=$-(state-type (quip effect state-type))
==
^- (quip effect state-type)
=^ effects-1=(list effect) state m-b
=^ effects-2=(list effect) state (fun state)
[(weld effects-1 effects-2) state]
::
++ once
|= =cord
=(cord (scot %uw nonce.state))
::
:: Subsystem to keep track of which ships to ping across breaches
:: and sponsorship changes
::
++ ships
|%
++ rind (^rind card state)
++ kick
|= [our=@p now=@da]
^- (quip card _state)
:: ?: =(%czar (clan:title our))
:: `state
::
:: NB: !! This includes our own ship, and for moons, this is
:: what has caused Jael to fetch our own rift from our parent.
:: This role may be taken by Ames's subscription to
:: %public-keys, but this must be tested before changing the
:: behavior here.
::
=/ new-ships (~(gas in *(set ship)) (saxo:title our now our))
=/ removed (~(dif in ships.state) new-ships)
=/ added (~(dif in new-ships) ships.state)
;< new-state=_state rind
?~ removed `state
[[%pass /jael %arvo %j %nuke removed]~ state]
=. state new-state
::
;< new-state=_state rind
?~ added `state
[[%pass /jael %arvo %j %public-keys added]~ state]
=. state new-state
::
:: Kick even if ships weren't added or removed
::
(kick-pings our now new-ships)
::
:: Kick whenever we get a response. We really care about
:: breaches and sponsorship changes.
::
:: Delay until next event in case of breach, so that ames can
:: clear its state.
::
++ take-jael
|= now=@da
^- (quip card _state)
[[%pass /jael/delay %arvo %b %wait now]~ state]
::
++ take-delay kick
--
::
:: Starts pinging a new set of `ships`.
::
++ kick-pings
|= [our=@p now=@da ships=(set ship)]
^- (quip card _state)
=: nonce.state +(nonce.state)
ships.state ships
==
::
?: ?=(%nat -.plan.state)
(kick:nat our)
(kick:pub our now)
::
:: Subsystem for pinging our sponsors when we might be behind a NAT
::
:: Ping each ship every 25 seconds to keep the pinhole open.
:: This is expensive, but if you don't do it and you are behind a
:: NAT, you will stop receiving packets from other ships except
:: during the 30 seconds following each packet you send.
::
++ nat
?> ?=(%nat -.plan.state)
|%
++ rind (^rind card state)
++ kick
|= our=@p
^- (quip card _state)
=/ ships ~(tap in ships.state)
|- ^- (quip card _state)
?~ ships `state
?: =(our i.ships) $(ships t.ships)
;< new-state=_state rind (send-ping i.ships)
=. state new-state
$(ships t.ships)
::
++ send-ping
|= =ship
^- (quip card _state)
:_ state
=/ wire /nat/(scot %uw nonce.state)/ping/(scot %p ship)
[%pass wire %agent [ship %ping] %poke %noun !>(~)]~
::
++ take-ping
|= [now=@da =wire error=(unit tang)]
^- (quip card _state)
?. ?=([%nat @ %ping @ ~] wire) `state
?. (once i.t.wire) `state
=/ ship (slav %p i.t.t.t.wire)
%- (slog ?~(error ~ ['ping: got nack' >ship< u.error]))
:_ state
=/ wire /nat/(scot %uw nonce.state)/wait/(scot %p ship)
[%pass wire %arvo %b %wait (add nat-timeout now)]~
::
++ take-wait
|= =wire
^- (quip card _state)
?. ?=([%nat @ %wait @ ~] wire) `state
?. (once i.t.wire) `state
=/ ship (slav %p i.t.t.t.wire)
(send-ping ship)
--
::
:: Subsystem for pinging our sponsors when we know we're not behind a NAT
::
:: Check our IP address every minute, and only if it changes,
:: ping all our sponsors.
::
++ pub
?> ?=(%pub -.plan.state)
|%
++ rind (^rind card state)
++ kick
|= [our=@p now=@da]
^- (quip card _state)
;< new-state=_state rind (send-pings our)
=. state new-state
::
;< new-state=_state rind check-ip
=. state new-state
::
(set-timer now)
::
++ send-pings
|= our=@p
^- (quip card _state)
:_ state
%+ murn ~(tap in ships.state)
|= =ship
?: =(our ship)
~
=/ wire /pub/(scot %uw nonce.state)/ping/(scot %p ship)
`u=[%pass wire %agent [ship %ping] %poke %noun !>(~)]
::
++ take-pings
|= [=wire error=(unit tang)]
^- (quip card _state)
?. ?=([%pub @ %ping @ ~] wire) `state
?. (once i.t.wire) `state
=/ ship (slav %p i.t.t.t.wire)
%- (slog ?~(error ~ ['ping: got nack' >ship< u.error]))
`state
::
++ check-ip
^- (quip card _state)
:_ state
=/ wire /pub/(scot %uw nonce.state)/ip
=/ =request:http [%'GET' ip-reflector ~ ~]
[%pass wire %arvo %i %request request *outbound-config:iris]~
::
++ take-ip
|= [our=@p =wire resp=client-response:iris]
^- (quip card _state)
?. ?=([%pub @ %ip ~] wire) `state
?. (once i.t.wire) `state
::
?. ?=(%finished -.resp) `state :: will retry in a minute
?. ?=(%200 status-code.response-header.resp)
=* s status-code.response-header.resp
%- (slog leaf+"ping: ip check failed: {<s>}" ~)
`state
::
?~ full-file.resp
%- (slog 'ping: ip check body empty' ~)
`state
::
=* body q.data.u.full-file.resp
?~ body
%- (slog 'ping: ip check body empty' ~)
`state
::
=/ ip (end [3 (dec (met 3 body))] body)
?: =(ip.plan.state `ip) `state
::
=. ip.plan.state `ip
(send-pings our)
::
++ set-timer
|= now=@da
^- (quip card _state)
=/ =wire /pub/(scot %uw nonce.state)/wait
[[%pass wire %arvo %b %wait (add ip-timeout now)]~ state]
::
++ take-wait
|= [our=@p now=@da =wire]
^- (quip card _state)
?. ?=([%pub @ %wait ~] wire) `state
?. (once i.t.wire) `state
;< new-state=_state rind check-ip
=. state new-state
::
(set-timer now)
--
--
++ galaxy-for
|= [=ship =bowl:gall]
^- @p
=/ next (sein:title our.bowl now.bowl ship)
?: ?=(%czar (clan:title next))
next
$(ship next)
::
++ wait-card
|= [=wire now=@da]
^- card
[%pass wire %arvo %b %wait (add nat-timeout now)]
::
++ ping
|= [=ship force=?]
^- (quip card _state)
?: &(!force (gth pokes.state 0) =(ship galaxy.state))
[~ state]
:_ state(pokes +(pokes.state), galaxy ship)
[%pass /ping %agent [ship %ping] %poke %noun !>(~)]~
--
%+ verb |
^- agent:gall
|_ =bowl:gall
@ -281,28 +66,73 @@
::
++ on-init
^- [(list card) _this]
=. plan.state [%nat ~]
=^ cards state (kick:ships our.bowl now.bowl)
[cards this]
=. mode.state %formal
=. pokes.state 0
=. galaxy.state (galaxy-for our.bowl bowl)
[~ this]
::
++ on-save !>(state)
++ on-load
|= old-vase=vase
|^
=/ old !<(state-any old-vase)
=? old ?=(%0 -.old) (state-0-to-1 old)
?> ?=(%1 -.old)
=? old ?=(%1 -.old) (state-1-to-2 old)
=? old ?=(%2 -.old) (state-2-to-3 old)
?> ?=(%3 -.old)
=. state old
=^ cards state (kick:ships our.bowl now.bowl)
[cards this]
[~ this]
::
+$ state-any $%(state-0 state-1)
+$ state-0 [%0 ships=(map ship [=rift =ship-state])]
+$ ship-state
$% [%idle ~]
[%poking ~]
[%http until=@da]
[%waiting until=@da]
==
+$ state-any $%(state-0 state-1 state-2 state-3)
+$ state-0 [%0 ships=(map ship [=rift =ship-state])]
+$ state-1
$: %1
ships=(set ship)
nonce=@ud
$= plan
$~ [%nat ~]
$% [%nat ~]
[%pub ip=(unit @t)]
== ==
+$ state-2
$: %2
ships=(set ship)
nonce=@ud
$= plan
$~ [%nat ~]
$% [%nat ~]
[%pub ip=(unit @t)]
[%off ~]
[%one ~]
==
==
::
++ state-0-to-1
|= old=state-0
^- state-1
[%1 ~ 0 %nat ~]
::
++ state-1-to-2
|= old=state-1
^- state-2
old(- %2)
::
++ state-2-to-3
|= old=state-2
^- state-3
:* %3 %formal 0 ~
=/ galaxy=(list @p)
%+ skim ~(tap in ships.old)
|=(p=@p ?=(%czar (clan:title p)))
?: =(1 (lent galaxy))
-.galaxy
(head (flop (^saxo:title our.bowl)))
==
--
:: +on-poke: positively acknowledge pokes
::
@ -311,20 +141,21 @@
?. =(our src):bowl :: don't crash, this is where pings are handled
`this
::
?: ?=(%czar (clan:title our.bowl))
`this
::
=^ cards state
?: =(q.vase %kick) :: NB: ames calls this on %born
(kick:ships our.bowl now.bowl)
?: =(q.vase %nat)
=. plan.state [%nat ~]
(kick:ships our.bowl now.bowl)
?: =(q.vase %no-nat)
=. plan.state [%pub ~]
(kick:ships our.bowl now.bowl)
?: ?=([%kick ?] q.vase)
=? mode.state =(+.q.vase %.y)
%formal
(ping (galaxy-for our.bowl bowl) %.n)
::
?: |(=(q.vase %once) =(q.vase %stop)) :: NB: ames calls this on %once
=. mode.state %informal
(ping (galaxy-for our.bowl bowl) %.y)
`state
[cards this]
::
++ on-watch on-watch:def
++ on-leave on-leave:def
++ on-peek
|= =path
^- (unit (unit cage))
@ -334,19 +165,18 @@
++ on-agent
|= [=wire =sign:agent:gall]
^- [(list card) _this]
=^ cards state
?+ wire `state
[%nat *]
?. ?=(%nat -.plan.state) `state
?. ?=(%poke-ack -.sign) `state
(take-ping:nat now.bowl wire p.sign)
::
[%pub *]
?. ?=(%pub -.plan.state) `state
?. ?=(%poke-ack -.sign) `state
(take-pings:pub wire p.sign)
==
[cards this]
?. ?=([%ping *] wire)
`this
?. ?=(%poke-ack -.sign)
`this
=. pokes.state (dec pokes.state)
?. =(pokes.state 0)
`this
?. |(?=(%formal mode.state) ?=(^ p.sign))
`this
=/ wir /wait
=. timer.state `[wir now.bowl]
[[(wait-card wir now.bowl)]~ this]
:: +on-arvo: handle timer firing
::
++ on-arvo
@ -354,36 +184,22 @@
^- [(list card) _this]
=^ cards state
?+ wire `state
[%jael %delay ~]
[%wait *]
?: ?=(%czar (clan:title our.bowl))
`state
?. ?=(%formal mode.state) `state
?> ?=(%wake +<.sign-arvo)
?^ error.sign-arvo
%- (slog 'ping: strange jael wake fail!' u.error.sign-arvo)
%- (slog 'ping: strange wake fail!' u.error.sign-arvo)
`state
(take-delay:ships our.bowl now.bowl)
=. timer.state ~
(ping (galaxy-for our.bowl bowl) %.n)
::
[%jael ~]
?> ?=(%public-keys +<.sign-arvo)
(take-jael:ships now.bowl)
::
[%nat *]
?. ?=(%nat -.plan.state) `state
?> ?=(%wake +<.sign-arvo)
?^ error.sign-arvo
%- (slog 'ping: strange nat wake fail!' u.error.sign-arvo)
`state
(take-wait:nat wire)
::
[%pub @ %ip *]
?. ?=(%pub -.plan.state) `state
?> ?=(%http-response +<.sign-arvo)
(take-ip:pub our.bowl wire client-response.sign-arvo)
::
[%pub @ %wait *]
?. ?=(%pub -.plan.state) `state
?> ?=(%wake +<.sign-arvo)
(take-wait:pub our.bowl now.bowl wire)
==
[cards this]
::
++ on-save !>(state)
++ on-fail on-fail:def
++ on-watch on-watch:def
++ on-leave on-leave:def
--

View File

@ -14,7 +14,7 @@
$: starting=(map yarn [=trying =vase])
running=(axal thread-form)
tid=(map tid yarn)
serving=(map tid [(unit @ta) =mark =desk])
serving=(map tid [(unit [rid=@ta take=?(%json %noun)]) =mark =desk])
scrying=(jug tid [=wire =ship =path])
==
::
@ -26,10 +26,20 @@
clean-slate-3
clean-slate-4
clean-slate-5
clean-slate-6
clean-slate
==
::
+$ clean-slate
$: %7
starting=(map yarn [=trying =vase])
running=(list yarn)
tid=(map tid yarn)
serving=(map tid [(unit [rid=@ta take=?(%json %noun)]) =mark =desk])
scrying=(jug tid [wire ship path])
==
::
+$ clean-slate-6
$: %6
starting=(map yarn [=trying =vase])
running=(list yarn)
@ -121,7 +131,8 @@
=. any (old-to-4 any)
=. any (old-to-5 any)
=. any (old-to-6 any)
?> ?=(%6 -.any)
=. any (old-to-7 any)
?> ?=(%7 -.any)
::
=. tid.state tid.any
=/ yarns=(list yarn)
@ -148,8 +159,8 @@
++ old-to-2
|= old=clean-slate-any
^- (quip card clean-slate-any)
?> ?=(?(%1 %2 %3 %4 %5 %6) -.old)
?: ?=(?(%2 %3 %4 %5 %6) -.old)
?> ?=(?(%1 %2 %3 %4 %5 %6 %7) -.old)
?: ?=(?(%2 %3 %4 %5 %6 %7) -.old)
`old
:- ~[bind-eyre:sc]
:* %2
@ -162,8 +173,8 @@
++ old-to-3
|= old=clean-slate-any
^- clean-slate-any
?> ?=(?(%2 %3 %4 %5 %6) -.old)
?: ?=(?(%3 %4 %5 %6) -.old)
?> ?=(?(%2 %3 %4 %5 %6 %7) -.old)
?: ?=(?(%3 %4 %5 %6 %7) -.old)
old
:* %3
starting.old
@ -175,8 +186,8 @@
++ old-to-4
|= old=clean-slate-any
^- clean-slate-any
?> ?=(?(%3 %4 %5 %6) -.old)
?: ?=(?(%4 %5 %6) -.old)
?> ?=(?(%3 %4 %5 %6 %7) -.old)
?: ?=(?(%4 %5 %6 %7) -.old)
old
:* %4
starting.old
@ -188,15 +199,15 @@
++ old-to-5
|= old=clean-slate-any
^- clean-slate-any
?> ?=(?(%4 %5 %6) -.old)
?: ?=(?(%5 %6) -.old) old
?> ?=(?(%4 %5 %6 %7) -.old)
?: ?=(?(%5 %6 %7) -.old) old
[%5 +.old(serving [serving.old ~])]
::
++ old-to-6
|= old=clean-slate-any
^- clean-slate
?> ?=(?(%5 %6) -.old)
?: ?=(%6 -.old) old
^- clean-slate-any
?> ?=(?(%5 %6 %7) -.old)
?: ?=(?(%6 %7) -.old) old
:- %6
%= +.old
scrying
@ -208,6 +219,16 @@
::
[/keen ship path]~
==
::
++ old-to-7
|= old=clean-slate-any
^- clean-slate-any
?> ?=(?(%6 %7) -.old)
?: ?=(%7 -.old) old
=- old(- %7, serving -)
%- ~(run by serving.old)
|= [request=(unit @ta) =mark =desk]
[(bind request (late %json)) mark desk]
--
::
++ on-poke
@ -309,15 +330,36 @@
=* input-mark i.t.t.site.url
=* thread i.t.t.t.site.url
=* output-mark i.t.t.t.t.site.url
=/ =tid (new-thread-id thread)
=. serving.state
(~(put by serving.state) tid [`eyre-id output-mark desk])
:: TODO: speed this up somehow. we spend about 15ms in this arm alone
::
=/ tube (convert-tube %json input-mark desk bowl)
?> ?=(^ body.request.inbound-request)
=/ body=json (need (de:json:html q.u.body.request.inbound-request))
=/ input=vase (slop !>(~) (tube !>(body)))
=/ test=$-(@t ?(%json %noun))
|= head=@t
=; type=(unit @t)
?:(=(`'application/x-urb-jam' type) %noun %json)
%+ bind
(get-header:http head header-list.request.inbound-request)
:(cork trip cass crip)
=/ give (test 'content-type')
=/ take (test 'accept')
::
=/ =tid (new-thread-id thread)
=. serving.state
(~(put by serving.state) tid [`[eyre-id take] output-mark desk])
::
=/ input=vase
%+ slop !>(~)
?- give
%json
=/ tube (convert-tube %json input-mark desk bowl)
=/ body=json (need (de:json:html q.u.body.request.inbound-request))
(tube !>(body))
::
%noun
=/ tube (convert-tube %noun input-mark desk bowl)
=/ body=noun (cue q.u.body.request.inbound-request)
(tube !>(body))
==
=/ boc bec
=/ =start-args:spider [~ `tid boc(q desk, r da+now.bowl) thread input]
(handle-start-thread start-args)
@ -490,8 +532,9 @@
^- [(list card) _state]
%+ roll cards.r
|= [=card cards=(list card) s=_state]
:_ =? scrying.s ?=([%pass ^ %arvo %a %keen @ *] card)
(~(put ju scrying.s) tid [&2 &6 |6]:card)
:_ =? scrying.s ?=([%pass ^ %arvo %a %keen ?(~ ^) @ *] card)
:: &2=wire &7=ship 7|=path
(~(put ju scrying.s) tid [&2 &7 |7]:card)
s
:_ cards
^- ^card
@ -549,18 +592,25 @@
=- (fall - `state)
%+ bind
(~(get by serving.state) tid)
|= [eyre-id=(unit @ta) output=mark =desk]
|= [request=(unit [rid=@ta take=?(%json %noun)]) output=mark =desk]
:_ state(serving (~(del by serving.state) tid))
?~ eyre-id
?~ request
~
%+ give-simple-payload:app:server u.eyre-id
%+ give-simple-payload:app:server rid.u.request
^- simple-payload:http
?. ?=(http-error:spider term)
%- (slog tang)
=/ tube (convert-tube %tang %json desk bowl)
:- [500 [['content-type' 'application/json'] ~]]
=- `(as-octs:mimes:html (en:json:html -))
o/(malt `(list [key=@t json])`[term+s/term tang+!<(json (tube !>(tang))) ~])
?- take.u.request
%json
=/ tube (convert-tube %tang %json desk bowl)
:- [500 [['content-type' 'application/json'] ~]]
=- `(as-octs:mimes:html (en:json:html -))
o/(malt `(list [key=@t json])`[term+s/term tang+!<(json (tube !>(tang))) ~])
::
%noun
:- [500 [['content-type' 'application/x-urb-jam'] ~]]
`(as-octs:mimes:html (jam [term tang]))
==
:_ ~ :_ ~
?- term
%bad-request 400
@ -587,13 +637,22 @@
=- (fall - `state)
%+ bind
(~(get by serving.state) tid)
|= [eyre-id=(unit @ta) output=mark =desk]
?~ eyre-id
|= [request=(unit [rid=@ta take=?(%json %noun)]) output=mark =desk]
?~ request
`state
=/ tube (convert-tube output %json desk bowl)
:_ state(serving (~(del by serving.state) tid))
%+ give-simple-payload:app:server u.eyre-id
(json-response:gen:server !<(json (tube vase)))
?- take.u.request
%json
=/ tube (convert-tube output %json desk bowl)
:_ state(serving (~(del by serving.state) tid))
%+ give-simple-payload:app:server rid.u.request
(json-response:gen:server !<(json (tube vase)))
::
%noun
:_ state(serving (~(del by serving.state) tid))
%+ give-simple-payload:app:server rid.u.request
:- [200 ['content-type' 'application/x-urb-jam']~]
`(as-octs:mimes:html (jam q.vase))
==
::
++ thread-done
|= [=yarn =vase silent=?]
@ -680,7 +739,7 @@
::
++ clean-state
!> ^- clean-slate
6+state(running (turn ~(tap of running.state) head))
7+state(running (turn ~(tap of running.state) head))
::
++ convert-tube
|= [from=mark to=mark =desk =bowl:gall]

View File

@ -2,7 +2,8 @@
!:
|%
+$ card card:agent:gall
+$ test ?(%agents %marks %generators)
+$ command $@(=test [=desk =test])
+$ test ?(%agents %marks %generators %threads)
+$ state
$: app=(set path)
app-ok=?
@ -10,9 +11,10 @@
mar-ok=?
gen=(set path)
gen-ok=?
ted=(set path)
ted-ok=?
==
--
=, format
^- agent:gall
=| =state
|_ =bowl:gall
@ -26,12 +28,16 @@
|= [=mark =vase]
^- [(list card) _this]
?> (team:title [our src]:bowl)
=+ !<(cmd=command vase)
=? cmd ?=(@ cmd)
[q.byk.bowl test.cmd]
?> ?=(^ cmd)
|^
=+ !<(=test vase)
?- test
%marks test-marks
%agents test-agents
?- test.cmd
%marks test-marks
%agents test-agents
%generators test-generators
%threads test-threads
==
::
++ test-marks
@ -51,7 +57,7 @@
|=(c=@tD `@tD`?:(=('/' c) '-' c))
=/ sing=card
:+ %pass /build/mar/[mak]
[%arvo %c %warp our.bowl q.byk.bowl ~ %sing %b da+now.bowl /[mak]]
[%arvo %c %warp our.bowl desk.cmd ~ %sing %b da+now.bowl /[mak]]
%_ $
paz t.paz
fex [sing fex]
@ -73,7 +79,7 @@
$(daz t.daz)
=/ sing=card
:+ %pass /build/app/[i.daz]
[%arvo %c %warp our.bowl q.byk.bowl ~ %sing %a da+now.bowl dap-pax]
[%arvo %c %warp our.bowl desk.cmd ~ %sing %a da+now.bowl dap-pax]
%_ $
daz t.daz
fex [sing fex]
@ -93,14 +99,33 @@
$(paz t.paz)
=/ sing=card
:+ %pass build+i.paz
[%arvo %c %warp our.bowl q.byk.bowl ~ %sing %a da+now.bowl i.paz]
[%arvo %c %warp our.bowl desk.cmd ~ %sing %a da+now.bowl i.paz]
%_ $
paz t.paz
fex [sing fex]
gen.state (~(put in gen.state) i.paz)
==
::
++ now-beak %_(byk.bowl r [%da now.bowl])
++ test-threads
=| fex=(list card)
^+ [fex this]
?> =(~ ted.state)
=. ted-ok.state %.y
=+ .^(paz=(list path) ct+(en-beam now-beak /ted))
|- ^+ [fex this]
?~ paz [(flop fex) this]
=/ xap=path (flop i.paz)
?. ?=([%hoon *] xap)
$(paz t.paz)
=/ sing=card
:+ %pass build+i.paz
[%arvo %c %warp our.bowl desk.cmd ~ %sing %a da+now.bowl i.paz]
%_ $
paz t.paz
fex [sing fex]
ted.state (~(put in ted.state) i.paz)
==
++ now-beak [our.bowl desk.cmd da+now.bowl]
--
++ on-watch on-watch:def
++ on-leave on-leave:def
@ -150,6 +175,15 @@
~? =(~ gen.state)
?:(gen-ok.state %all-generators-built %some-generators-failed)
[~ this]
::
[%ted *]
=/ ok ?=(^ p.sign-arvo)
%- (report path ok)
=? ted-ok.state !ok %.n
=. ted.state (~(del in ted.state) path)
~? =(~ ted.state)
?:(ted-ok.state %all-threads-built %some-threads-failed)
[~ this]
==
++ on-fail on-fail:def
--

View File

@ -0,0 +1,161 @@
:: verb-logger: serializes verb-plus events to unix-side json
::
:: watches specified agents for "verb plus" events, buffers those, and
:: periodically (+write-interval) flushes them out to unix, under the
:: .urb/put/verb-logger/[agent] directory of the pier.
::
/+ verb, dbug, verb-json
::
|%
+$ state-0
$: %0
events=(jar dude:gall event-plus:verb)
==
::
+$ card card:agent:gall
::
++ write-interval ~h1
::
++ write-events
|= [our=ship =dude:gall events=(list event-plus:verb)]
^- (list card)
?: =(~ events) ~ ::NOTE tmi
=/ first=event-plus:verb
(rear events)
=/ pax=path
/verb-logger/[dude]/(crip (a-co:co (unm:chrono:userlib now.first)))/json
=/ vex=@
(en:json:html (events:enjs our dude events))
[%pass /write/[dude] %agent [our %hood] %poke %drum-put !>([pax vex])]~
::
++ ingest-event
|= $: our=ship
events=(jar dude:gall event-plus:verb)
[=dude:gall event=event-plus:verb]
==
^- (quip card _events)
?~ ves=(~(get ja events) dude)
:- ~
(~(put by events) dude [event ~])
?: .= (sub now.i.ves (mod now.i.ves write-interval))
(sub now.event (mod now.event write-interval))
:- ~
(~(put by events) dude [event ves])
:- (write-events our dude ves)
(~(put by events) dude [event ~])
::
++ enjs
=, enjs:format
|%
++ events
|= [our=@p =dude:gall events=(list event-plus:verb)] :: latest-first
=/ first=event-plus:verb (rear events)
%- pairs
:~ 'ship'^s+(scot %p our)
'dude'^s+dude
'from'^(time now.first)
::
:- 'events'
:- %a
%+ roll events ::NOTE we +roll to +turn & +flop simultaneously
|= [event=event-plus:verb out=(list json)]
[(event:enjs:verb-json event) out]
==
--
--
::
=| state-0
=* state -
::
%+ verb |
%- agent:dbug
|_ =bowl:gall
+* this .
::
++ on-init
^- (quip card _this)
[~ this]
::
++ on-save
!>(state)
::
++ on-load
|= ole=vase
^- (quip card _this)
[~ this(state !<(state-0 ole))]
::
++ on-poke
|= [=mark =vase]
^- (quip card _this)
?> =(%noun mark)
?+ q.vase !!
[%watch =dude:gall]
=* dude dude.q.vase
:_ this
[%pass /log/[dude] %agent [our.bowl dude] %watch /verb/events-plus]~
::
[%leave =dude:gall]
=* dude dude.q.vase
:- :- [%pass /log/[dude] %agent [our.bowl dude] %leave ~]
(write-events our.bowl dude (~(get ja events) dude))
this(events (~(del by events) dude))
::
[%flush =dude:gall]
|-
=* dude dude.q.vase
?. =(%$ dude)
:- (write-events our.bowl dude (~(get ja events) dude))
this(events (~(del by events) dude))
=| cards=(list card)
=/ dudes=(list dude:gall) ~(tap in ~(key by events))
|- ^- (quip card _this)
?~ dudes [cards this]
=^ caz this ^$(dude.q.vase i.dudes)
=. cards (weld cards caz)
$(dudes t.dudes)
==
::
++ on-agent
|= [=wire =sign:agent:gall]
^- (quip card _this)
~| wire
?+ wire !!
[%write @ ~]
=* dude i.t.wire
?> ?=(%poke-ack -.sign)
?~ p.sign [~ this]
%. [~ this]
%- %*(. slog pri 3)
[(cat 3 'verb-logger: lost export for %' dude) u.p.sign]
::
[%log @ ~]
=* dude i.t.wire
?- -.sign
%poke-ack !!
%kick =- [[-]~ this]
[%pass /log/[dude] %agent [our.bowl dude] %watch /verb/events-plus]
%watch-ack ?~ p.sign [~ this]
%. [~ this]
%- %*(. slog pri 2)
[(cat 3 'verb-logger: failed verb watch for %' dude) u.p.sign]
%fact ?> =(%verb-event-plus p.cage.sign)
=^ caz events
%- ingest-event
[our.bowl events dude !<(event-plus:verb q.cage.sign)]
[caz this]
==
==
::
++ on-fail
|= [=term =tang]
^- (quip card _this)
%. [~ this]
%- %*(. slog pri 3)
:_ tang
(cat 3 'verb-logger: dropping the ball: ' term)
::
++ on-watch |=(* !!)
++ on-leave |=(* !!)
++ on-arvo |=(* !!)
++ on-peek |=(* ~)
--

View File

@ -0,0 +1,16 @@
:: make a unix commit event
::
:: call as > .event/jam +commit-event /path/to/file
:: to be used with ./urbit-binary -I event.jam pier
::
:: XX expand with arbitrary user-defined events?
:: XX only supports files in which +noun:grab in the mark file returns a @t
:: (e.g. hoon files)
::
:- %say
|= [[now=@da eny=@uvJ bec=beak] [=path ~] ~]
:- %noun
?~ beam=(de-beam path)
~|(%path-not-beam !!)
=+ .^(file=@t %cx path)
[/c/sync %info desk=q.u.beam & [s.u.beam %ins %mime !>([/ (as-octs:mimes:html file)])]~]

View File

@ -0,0 +1,6 @@
:- %say
|= $: [now=@da eny=@uvJ bec=beak]
[syd=desk her=ship sud=desk approve=? ~]
~
==
kiln-approve-merge+[[syd her sud] approve]

View File

@ -2,16 +2,36 @@
::
:::: /hoon/cp/hood/gen
::
:: XX clay discards the type, so %noun is used
:: copy by lobe should be used, if implemented
::
/? 310
:- %say
=, space:userlib
|= [^ [input=path output=path ~] ~]
|= [^ [input=path output=path ~] r=_|]
:- %kiln-info
?. =(-:(flop input) -:(flop output))
["Can't move to a different mark" ~]
=+ dir=.^(arch %cy input)
?~ fil.dir
~& "No such file:"
[<input> ~]
:- "copied"
`(foal output -:(flop input) [%atom %t ~] .^(* %cx input)) :: XX type
^- [mez=tape tor=(unit toro:clay)]
?. r
?. =(-:(flop input) -:(flop output))
["Can't move to a different mark" ~]
?~ =<(fil .^(arch %cy input))
~& "No such file:"
[<input> ~]
:- "copied"
`(foal output -:(flop input) [%noun .^(* %cx input)])
?~ in-beam=(de-beam input) ["bad input path" ~]
?~ =<(dir .^(arch %cy input)) ["input path isn't a directory" ~]
?~ out-beam=(de-beam output) ["bad output path" ~]
=/ in-beak=beak [p q r]:u.in-beam
=/ out-beak=beak [p q r]:u.out-beam
=/ =soba:clay
%+ murn .^((list path) %ct input)
|= pax=path
?: =(1 (sub (lent pax) (lent s.u.in-beam))) ~
=/ =cage
:- -:(flop pax)
[%noun .^(* %cx (en-beam in-beak pax))]
=/ =spur (weld s.u.out-beam (slag (lent s.u.in-beam) pax))
`[spur (feel (en-beam out-beak spur) cage)]
?~ soba ["nothing to copy" ~]
["copied" `[q.out-beak [%& soba]]]

View File

@ -0,0 +1,6 @@
:- %say
|= $: [now=@da eny=@uvJ bec=beak]
[auto=? ~]
~
==
kiln-global-automerge+auto

View File

@ -0,0 +1,6 @@
:- %say
|= $: [now=@da eny=@uvJ bec=beak]
[old=dock new=dock ~]
~
==
kiln-jump-opt+[old new &]

View File

@ -0,0 +1,16 @@
/+ *generators
:- %ask
|= $: [now=@da eny=@uvJ bec=beak]
[syd=desk her=ship sud=desk ~]
hard=_|
==
?: hard (produce %kiln-jump-propose syd her sud)
=/ msg
leaf+"Are you sure you want to tell subscribers to get ".
"updates for {<syd>} from {<her>}/{(trip sud)}?"
%+ print msg
%+ prompt [%& %prompt "(y/N) "]
|= in=tape
?. |(=("y" in) =("Y" in) =("yes" in))
no-product
(produce %kiln-jump-propose syd her sud)

View File

@ -0,0 +1,6 @@
:- %say
|= $: [now=@da eny=@uvJ bec=beak]
[old=dock new=dock ~]
~
==
kiln-jump-opt+[old new |]

View File

@ -24,6 +24,14 @@
?^ arg
mon.arg
(add our (lsh 5 (end 5 (shaz eny))))
=/ ryf=(unit rift)
.^((unit rift) %j /(scot %p p.bec)/ryft/(scot %da now)/(scot %p mon))
?^ ryf
%. ~
%- slog
:~ leaf+"can't create {(scow %p mon)}, it already exists."
'use |moon-breach and/or |moon-cycle-keys instead.'
==
=/ seg=ship (sein:title our now mon)
?. =(our seg)
%- %- slog :_ ~

View File

@ -0,0 +1,6 @@
:- %say
|= $: [now=@da eny=@uvJ bec=beak]
[syd=desk her=ship sud=desk auto=(unit ?) ~]
~
==
kiln-sync-automerge+[[syd her sud] auto]

16
pkg/arvo/gen/jumps.hoon Normal file
View File

@ -0,0 +1,16 @@
/- h=hood
:- %say
|= [[now=@da eny=@uvJ bec=beak] ~ ~]
:- %tang
^- tang
=+ .^ hop=jump:h
%gx
(scot %p p.bec)
%hood
(scot %da now)
/kiln/jumps/noun
==
?> ?=(%all -.hop)
%+ turn ~(tap by all.hop)
|= [old=dock new=dock]
leaf+"{<p.old>}/{(trip q.old)} -> {<p.new>}/{(trip q.new)}"

16
pkg/arvo/gen/updates.hoon Normal file
View File

@ -0,0 +1,16 @@
/- h=hood
:- %say
|= [[now=@da eny=@uvJ bec=beak] ~ ~]
:- %tang
^- tang
=+ .^ upd=sync-update:h
%gx
(scot %p p.bec)
%hood
(scot %da now)
/kiln/pending/noun
==
?> ?=(%pending -.upd)
%+ turn ~(tap in pending.upd)
|= [sync-record:h rev=@ud]
leaf+"{<syd>} <- {<her>}/{(trip sud)}/{<rev>}"

View File

@ -2,6 +2,6 @@
:- %say
|= [[now=@da eny=@uvJ bec=beak] [syd=desk ~] verb=_&]
:* %tang
leaf+"Notice: +vat is deprecated. use +vats which now takes one or more desks as arguments. e.g. '+vats %base %garden'"
leaf+"Notice: +vat is deprecated. use +vats which now takes one or more desks as arguments. e.g. '+vats %base %landscape'"
(report-vat (report-prep p.bec now) p.bec now syd verb)
==

View File

@ -17,12 +17,4 @@
[filt=@tas verb=_|]
==
:- %tang ^- tang
?. &(=(~ deks) =(%$ filt))
(report-vats p.bec now deks filt verb)
%- zing
%+ turn
%+ sort
=/ sed .^((set desk) %cd /(scot %p p.bec)//(scot %da now))
(sort ~(tap in sed) |=([a=@ b=@] !(aor a b)))
|=([a=desk b=desk] ?|(=(a %kids) =(b %base)))
|=(syd=desk (report-vat (report-prep p.bec now) p.bec now syd verb))
(report-vats p.bec now deks filt verb)

View File

@ -1070,8 +1070,9 @@
++ wrd :: next or current word
|= a=(list @)
=| i=@ud
?~ a i
|- ^- @ud
?: |(?=(~ a) (alnm i.a)) i
?: |(?=(~ t.a) (alnm i.a)) i
$(i +(i), a t.a)
--
--

View File

@ -5,7 +5,8 @@
=, format
=* dude dude:gall
|%
+$ state state-10
+$ state state-11
+$ state-11 [%11 pith-11]
+$ state-10 [%10 pith-10]
+$ state-9 [%9 pith-9]
+$ state-8 [%8 pith-9]
@ -19,7 +20,8 @@
+$ state-0 [%0 pith-0]
+$ any-state
$~ *state
$% state-10
$% state-11
state-10
state-9
state-8
state-7
@ -32,10 +34,32 @@
state-0
==
::
+$ pith-11
$: rem=(map desk per-desk)
nyz=@ud
zyn=(map sync-record sync-state)
:: requests from publishers to switch sync source
hop=(map dock dock)
:: toggle global update auto-merge
mer=?
::
commit-timer=[way=wire nex=@da tim=@dr mon=term]
:: map desk to the currently ongoing fuse request
:: and the latest version numbers for beaks to
fus=(map desk per-fuse)
:: used for fuses - every time we get a fuse we
:: bump this. used when calculating hashes to
:: ensure they're unique even when the same
:: request is made multiple times.
hxs=(map desk @ud)
==
::
+$ sync-state-10 [nun=@ta kid=(unit desk) let=@ud]
::
+$ pith-10
$: rem=(map desk per-desk)
nyz=@ud
zyn=(map kiln-sync sync-state)
zyn=(map sync-record sync-state-10)
commit-timer=[way=wire nex=@da tim=@dr mon=term]
:: map desk to the currently ongoing fuse request
:: and the latest version numbers for beaks to
@ -50,7 +74,7 @@
+$ pith-9
$: wef=(unit weft)
rem=(map desk per-desk)
syn=(map kiln-sync let=@ud)
syn=(map sync-record let=@ud)
ark=(map desk arak-9)
commit-timer=[way=wire nex=@da tim=@dr mon=term]
:: map desk to the currently ongoing fuse request
@ -78,7 +102,7 @@
+$ pith-7
$: wef=(unit weft)
rem=(map desk per-desk)
syn=(map kiln-sync let=@ud)
syn=(map sync-record let=@ud)
ark=(map desk arak-7)
commit-timer=[way=wire nex=@da tim=@dr mon=term]
:: map desk to the currently ongoing fuse request
@ -121,7 +145,7 @@
+$ pith-6
$: wef=(unit weft)
rem=(map desk per-desk) ::
syn=(map kiln-sync let=@ud) ::
syn=(map sync-record let=@ud) ::
ark=(map desk arak-6) ::
commit-timer=[way=wire nex=@da tim=@dr mon=term] ::
:: map desk to the currently ongoing fuse request
@ -139,7 +163,7 @@
::
+$ pith-5
$: rem=(map desk per-desk) ::
syn=(map kiln-sync let=@ud) ::
syn=(map sync-record let=@ud) ::
ark=(map desk arak-6) ::
commit-timer=[way=wire nex=@da tim=@dr mon=term] ::
:: map desk to the currently ongoing fuse request
@ -154,7 +178,7 @@
::
+$ pith-4 ::
$: rem=(map desk per-desk) ::
syn=(map kiln-sync let=@ud) ::
syn=(map sync-record let=@ud) ::
ark=(map desk arak-4) ::
commit-timer=[way=wire nex=@da tim=@dr mon=term] ::
:: map desk to the currently ongoing fuse request
@ -175,7 +199,7 @@
==
+$ pith-3 ::
$: rem=(map desk per-desk) ::
syn=(map kiln-sync let=@ud) ::
syn=(map sync-record let=@ud) ::
ark=(map desk arak-3) ::
commit-timer=[way=wire nex=@da tim=@dr mon=term] ::
:: map desk to the currently ongoing fuse request
@ -201,7 +225,7 @@
::
+$ pith-2 ::
$: rem=(map desk per-desk) ::
syn=(map kiln-sync let=@ud) ::
syn=(map sync-record let=@ud) ::
ota=(unit [=ship =desk =aeon]) ::
commit-timer=[way=wire nex=@da tim=@dr mon=term] ::
fus=(map desk per-fuse)
@ -209,13 +233,13 @@
== ::
+$ pith-1 ::
$: rem=(map desk per-desk) ::
syn=(map kiln-sync let=@ud) ::
syn=(map sync-record let=@ud) ::
ota=(unit [=ship =desk =aeon]) ::
commit-timer=[way=wire nex=@da tim=@dr mon=term] ::
== ::
+$ pith-0 ::
$: rem=(map desk per-desk) ::
syn=(map kiln-sync let=@ud) ::
syn=(map sync-record let=@ud) ::
autoload-on=? ::
cur-hoon=@uvI ::
cur-arvo=@uvI ::
@ -245,16 +269,6 @@
pot=term ::
==
+$ kiln-unmount $@(term [knot path]) ::
+$ kiln-sync ::
$: syd=desk :: local desk
her=ship :: foreign ship
sud=desk :: foreign desk
==
+$ kiln-unsync ::
$: syd=desk :: local desk
her=ship :: foreign ship
sud=desk :: foreign desk
==
+$ kiln-merge ::
$@ ~
$: syd=desk ::
@ -285,7 +299,6 @@
+$ kiln-fuse-list (unit desk)
--
|= [bowl:gall state]
?> =(src our)
=| moz=(list card:agent:gall)
|%
++ kiln .
@ -440,7 +453,7 @@
=^ cards-9=(list card:agent:gall) old
?. ?=(%9 -.old)
`old
=/ syn=(set kiln-sync)
=/ syn=(set sync-record)
%- ~(gas in ~(key by syn.old))
%+ murn ~(tap by ark.old)
|= [=desk =arak-9]
@ -468,8 +481,8 @@
[%pass /kiln/load-zest %arvo %c %zest desk zest]
::
%+ turn ~(tap in syn)
|= k=kiln-sync
[%pass /kiln/load-sync %agent [our %hood] %poke %kiln-sync !>(k)]
|= r=sync-record
[%pass /kiln/load-sync %agent [our %hood] %poke %kiln-sync !>(r)]
::
=/ ks ~(tap in syn)
|- ^- (list card:agent:gall)
@ -483,7 +496,17 @@
$(ks t.ks)
==
::
?> ?=(%10 -.old)
=? old ?=(%10 -.old)
%= old
- %11
|4 [hop=~ mer=& |4.old]
zyn %- ~(run by zyn.old)
|= sync-state-10
^- sync-state
[nun kid let ~ ~ |]
==
::
?> ?=(%11 -.old)
=. state old
abet:(emil cards-9)
::
@ -499,18 +522,31 @@
=/ ver (mergebase-hashes our %base now (~(got by sources) %base))
``noun+!>(?~(ver 0v0 i.ver))
::
[%x %kiln %syncs ~] ``noun+!>(zyn)
[%x %kiln %sources ~] ``noun+!>(sources)
[%x %kiln %jumps ~] ``kiln-jump+!>([%all hop])
[%x %kiln %syncs ~] ``noun+!>(zyn)
[%x %kiln %sources ~] ``noun+!>(sources)
[%x %kiln %automerge ~] ``loob+!>(mer)
[%x %kiln %pikes ~]
=+ .^(=rock:tire %cx /(scot %p our)//(scot %da now)/tire)
:^ ~ ~ %kiln-pikes
!> ^- pikes
%- ~(rut by rock)
%- ~(urn by rock)
|= [=desk =zest wic=(set weft)]
^- pike
=+ .^(hash=@uv %cz /(scot %p our)/[desk]/(scot %da now))
=/ sync (~(get by sources) desk)
[sync hash zest wic]
::
[%x %kiln %pending ~]
:^ ~ ~ %kiln-sync-update
!> ^- sync-update
:- %pending
%- ~(gas by *(set [sync-record @ud]))
^- (list [sync-record @ud])
%+ murn ~(tap by zyn)
|= [sync-record sync-state]
?~ hav ~
(some [syd her sud] u.hav)
==
::
:: +get-germ: select merge strategy into local desk
@ -528,12 +564,15 @@
::
++ poke
|= [=mark =vase]
?> |(=(src our) =(%kiln-jump-ask mark))
?+ mark ~|([%poke-kiln-bad-mark mark] !!)
%kiln-approve-merge =;(f (f !<(_+<.f vase)) poke-approve-merge)
%kiln-autocommit =;(f (f !<(_+<.f vase)) poke-autocommit)
%kiln-bump =;(f (f !<(_+<.f vase)) poke-bump)
%kiln-cancel =;(f (f !<(_+<.f vase)) poke-cancel)
%kiln-cancel-autocommit =;(f (f !<(_+<.f vase)) poke-cancel-autocommit)
%kiln-commit =;(f (f !<(_+<.f vase)) poke-commit)
%kiln-sync-automerge =;(f (f !<(_+<.f vase)) poke-sync-automerge)
%kiln-fuse =;(f (f !<(_+<.f vase)) poke-fuse)
%kiln-fuse-list =;(f (f !<(_+<.f vase)) poke-fuse-list)
%kiln-gall-sear =;(f (f !<(_+<.f vase)) poke-gall-sear)
@ -543,12 +582,16 @@
%kiln-label =;(f (f !<(_+<.f vase)) poke-label)
%kiln-merge =;(f (f !<(_+<.f vase)) poke-merge)
%kiln-mount =;(f (f !<(_+<.f vase)) poke-mount)
%kiln-jump-ask =;(f (f !<(_+<.f vase)) poke-jump-ask)
%kiln-jump-opt =;(f (f !<(_+<.f vase)) poke-jump-opt)
%kiln-jump-propose =;(f (f !<(_+<.f vase)) poke-jump-propose)
%kiln-nuke =;(f (f !<(_+<.f vase)) poke-nuke)
%kiln-pause =;(f (f !<(_+<.f vase)) poke-pause)
%kiln-permission =;(f (f !<(_+<.f vase)) poke-permission)
%kiln-revive =;(f (f !<(_+<.f vase)) poke-revive)
%kiln-rein =;(f (f !<(_+<.f vase)) poke-rein)
%kiln-rm =;(f (f !<(_+<.f vase)) poke-rm)
%kiln-global-automerge =;(f (f !<(_+<.f vase)) poke-global-automerge)
%kiln-schedule =;(f (f !<(_+<.f vase)) poke-schedule)
%kiln-suspend =;(f (f !<(_+<.f vase)) poke-suspend)
%kiln-suspend-many =;(f (f !<(_+<.f vase)) poke-suspend-many)
@ -559,6 +602,19 @@
%kiln-unsync =;(f (f !<(_+<.f vase)) poke-unsync)
==
::
++ poke-approve-merge
|= [sync-record approve=?]
?~ got=(~(get by zyn) syd her sud)
=+ msg="kiln: no syncs from {(scow %p her)}/{(trip sud)} to {<syd>}"
((slog leaf+msg ~) abet)
?~ hav.u.got
=+ msg="kiln: no updates from {(scow %p her)}/{(trip sud)} for {<syd>}"
((slog leaf+msg ~) abet)
=< abet
?. approve
abet:drop:(sync syd her sud)
abet:(merg /main syd):(sync syd her sud)
::
++ poke-autocommit
|= [mon=kiln-commit auto=?]
=< abet
@ -679,6 +735,23 @@
|= =ship
abet:(emit %pass /kiln %arvo %g %sear ship)
::
++ poke-global-automerge
|= auto=?
=. mer auto
?. mer abet
=/ zyns=(list [sync-record sync-state]) ~(tap by zyn)
=< abet
|-
?~ zyns ..abet
?. ?& ?=(^ hav.i.zyns)
!?=([~ %.n] nit.i.zyns)
==
$(zyns t.zyns)
%= $
zyns t.zyns
..abet abet:(merg /main syd):(sync -.i.zyns)
==
::
++ poke-info
|= [mez=tape tor=(unit toro)]
?~ tor
@ -692,17 +765,20 @@
?~ got=(~(get by rock) loc)
%dead
zest.u.got
=. zyn
=. ..abet
?~ got=(~(get by sources) loc)
zyn
(~(del by zyn) loc u.got)
..abet
?: =([her rem] u.got)
..abet
=. ..abet abet:drop:(sync loc u.got)
..abet(zyn (~(del by zyn) loc u.got))
=? ..abet ?=(%dead zest)
(emit %pass /kiln/install %arvo %c %zest loc ?:(=(our her) %live %held))
?: (~(has by zyn) loc her rem)
abet:(spam (render "already syncing" loc her rem ~) ~)
?: =([our loc] [her rem])
abet
=/ sun (sync loc her rem)
=/ sun okay:(sync loc her rem)
~> %slog.(fmt "beginning install into {here:sun}")
=< abet:abet:init
?: =(%base loc)
@ -710,7 +786,7 @@
sun
::
++ poke-kids
|= [hos=kiln-sync nex=(unit desk)]
|= [hos=sync-record nex=(unit desk)]
abet:abet:(apex:(sync hos) nex)
::
++ poke-label
@ -731,6 +807,84 @@
abet:(spam leaf+- ~)
abet:(emit %pass /mount %arvo %c [%mont pot u.bem])
::
++ poke-jump-propose
|= [syd=desk her=ship sud=desk]
?: =([our syd] [her sud])
abet
=/ let=@ud ud:.^(cass:clay %cw /(scot %p our)/[syd]/(scot %da now))
=/ subs=(set [@p rave:clay])
.^((set [@p rave:clay]) %cx /(scot %p our)//(scot %da now)/cult/[syd])
=/ ships=(set @p)
%+ roll ~(tap in subs)
|= [[=ship =rave:clay] ships=(set @p)]
?: =(our ship) ships
?. ?=([%sing %w [%ud @] ~] rave) ships
?. =(+(let) p.case.mood.rave) ships
(~(put in ships) ship)
=< abet
%- emil
%+ turn ~(tap in ships)
|= =ship
:* %pass /kiln/jump-propose %agent [ship %hood]
%poke %kiln-jump-ask !>([[our syd] [her sud]])
==
::
++ poke-jump-ask
|= [old=dock new=dock]
?> |(=(src p.old) =(src our))
?: =(old new)
?~ had=(~(get by hop) old)
abet
=. hop (~(del by hop) old)
abet:(emit %give %fact ~[/jumps] %kiln-jump !>([%nay old u.had]))
?~ (skim ~(tap by sources) |=(sync-record =(old [her sud])))
~> %slog.(fmt "no syncs from {(scow %p p.old)}/{(trip q.old)}")
abet
=. hop (~(put by hop) old new)
abet:(emit %give %fact ~[/jumps] %kiln-jump !>([%add old new]))
::
++ poke-jump-opt
|= [old=dock new=dock yea=?]
?~ got=(~(get by hop) old)
~> %slog.(fmt "no jump request for {(scow %p p.old)}/{(trip q.old)}")
abet
?. =(new u.got)
=/ txt-old "{(scow %p p.old)}/{(trip q.old)}"
=/ txt-new "{(scow %p p.new)}/{(trip q.new)}"
~> %slog.(fmt "no jump request from {txt-old} to {txt-new}")
abet
?. yea
=/ txt-old "{(scow %p p.old)}/{(trip q.old)}"
=/ txt-new "{(scow %p p.new)}/{(trip q.new)}"
~> %slog.(fmt "denied jump from {txt-old} to {txt-new}")
=. hop (~(del by hop) old)
abet:(emit %give %fact ~[/jumps] %kiln-jump !>([%nay old new]))
=/ old-sources=(list sync-record)
(skim ~(tap by sources) |=(sync-record =(old [her sud])))
=/ new-sources=(list sync-record)
(turn old-sources |=(sync-record [syd new]))
=. ..abet
(emit %give %fact ~[/jumps] %kiln-jump !>([%yea old new]))
=. ..abet
|-
?~ old-sources
..abet
=. ..abet abet:drop:(sync i.old-sources)
=. zyn (~(del by zyn) i.old-sources)
$(old-sources t.old-sources, ..abet ..abet)
=. hop (~(del by hop) old)
=< abet
|- ^+ ..abet
?~ new-sources ..abet
%= $
new-sources t.new-sources
..abet =/ sun (sync i.new-sources)
=< abet:init
?: =(%base syd.i.new-sources)
(apex:sun `%kids)
sun
==
::
++ poke-nuke
|= [=term desk=?]
=< abet
@ -801,11 +955,28 @@
|=(=desk [%pass /kiln/suspend %arvo %c %zest desk %dead])
::
++ poke-sync
|= hos=kiln-sync
?: (~(has by zyn) hos)
abet:(spam (render "already syncing" [sud her syd ~]:hos) ~)
~> %slog.(fmt "beginning sync into {<syd.hos>} from {<her.hos>}/{<sud.hos>}")
abet:abet:init:(sync hos)
|= sync-record
?: (~(has by zyn) sud her syd)
abet:(spam (render "already syncing" [sud her syd ~]) ~)
=. ..abet
?~ got=(~(get by sources) syd)
..abet
=. ..abet abet:drop:(sync syd u.got)
..abet(zyn (~(del by zyn) syd u.got))
~> %slog.(fmt "beginning sync into {<syd>} from {<her>}/{<sud>}")
abet:abet:init:(sync syd her sud)
::
++ poke-sync-automerge
|= [sync-record auto=(unit ?)]
?~ got=(~(get by zyn) syd her sud)
=+ msg="kiln: no syncs from {(scow %p her)}/{(trip sud)} to {<syd>}"
((slog leaf+msg ~) abet)
=. zyn (~(put by zyn) [syd her sud] u.got(nit auto))
?~ hav.u.got
abet
?. |(?=([~ %.y] auto) &(mer ?=(~ auto)))
abet
abet:abet:(merg /main syd):(sync [syd her sud])
::
++ poke-syncs :: print sync config
|= ~
@ -813,7 +984,7 @@
?: =(0 ~(wyt by zyn))
[%leaf "no syncs configured"]~
%+ turn ~(tap by zyn)
|= [kiln-sync sync-state]
|= [sync-record sync-state]
(render "sync configured" sud her syd kid)
::
++ poke-uninstall
@ -841,9 +1012,10 @@
:: Don't need to cancel anything because new syncs will get a new nonce
::
++ poke-unsync
|= hus=kiln-unsync
|= hus=sync-record
?~ got=(~(get by zyn) hus)
abet:(spam (render "not syncing" [sud her syd ~]:hus) ~)
=. ..abet abet:drop:(sync hus)
=. zyn (~(del by zyn) hus)
abet:(spam (render "cancelling sync" sud.hus her.hus syd.hus kid.u.got) ~)
:: +peer: handle %watch
@ -851,10 +1023,26 @@
++ peer
|= =path
?> (team:title our src)
?: =(0 1) abet :: avoid mint-vain
?+ path ~|(kiln-path/path !!)
[%vats ~]
(mean leaf+"kiln: old subscription to /kiln/vats failed" ~)
::
[%jumps ~]
abet:(emit %give %fact ~ %kiln-jump !>([%all hop]))
::
[%updates ~]
=< abet
%- emit
:^ %give %fact ~
:- %kiln-sync-update
!> ^- sync-update
:- %pending
%- ~(gas by *(set [sync-record @ud]))
^- (list [sync-record @ud])
%+ murn ~(tap by zyn)
|= [sync-record sync-state]
?~ hav ~
(some [syd her sud] u.hav)
==
::
++ take-agent
@ -864,6 +1052,8 @@
~? ?=(^ p.sign) [%kiln-poke-nack u.p.sign]
abet
~|([%kiln-bad-take-agent wire -.sign] !!)
::
[%change-publisher ~] abet
::
[%fancy *]
?> ?=(%poke-ack -.sign)
@ -1078,15 +1268,30 @@
abet:abet:(take:(sync syd her sud) t.t.t.wire sign-arvo)
::
++ sync
|= kiln-sync
|= sync-record
=/ got (~(get by zyn) syd her sud)
=+ `sync-state`(fall got [(scot %uv nyz) ~ *@ud])
=+ `sync-state`(fall got [(scot %uv nyz) ~ *@ud ~ ~ |])
=? nyz ?=(~ got) +(nyz)
|%
++ abet ..sync(zyn (~(put by zyn) [syd her sud] nun kid let))
++ abet ..sync(zyn (~(put by zyn) [syd her sud] nun kid let nit hav yea))
++ apex |=(nex=(unit desk) ..abet(kid nex))
++ emit |=(card:agent:gall ..abet(kiln (^emit +<)))
++ emil |=((list card:agent:gall) ..abet(kiln (^emil +<)))
++ okay ..abet(yea &)
++ gain
=. hav `(dec let)
=/ upd=sync-update [%new [syd her sud] (dec let)]
(emit %give %fact ~[/update] %kiln-sync-update !>(upd))
++ drop
=? ..abet ?=(^ hav)
=/ upd=sync-update [%drop [syd her sud] u.hav]
(emit %give %fact ~[/updates] %kiln-sync-update !>(upd))
..abet(hav ~, yea |)
++ tada
=? ..abet ?=(^ hav)
=/ upd=sync-update [%done [syd her sud] u.hav]
(emit %give %fact ~[/updates] %kiln-sync-update !>(upd))
..abet(hav ~, yea |)
++ here "{<syd>} from {<her>}/{<sud>}"
++ ware
|= =wire
@ -1101,7 +1306,6 @@
%merg desk her sud
ud+(dec let) (get-germ desk)
==
::
:: (re)Start a sync from scratch by finding what version the source
:: desk is at
::
@ -1129,8 +1333,8 @@
?> ?=(^ riot)
:: The syncs may have changed, so get the latest
::
;< zyx=(map kiln-sync sync-state) bind:m
(scry:strandio (map kiln-sync sync-state) /gx/hood/kiln/syncs/noun)
;< zyx=(map sync-record sync-state) bind:m
(scry:strandio (map sync-record sync-state) /gx/hood/kiln/syncs/noun)
?. (~(has by zyx) syd her sud)
(pure:m !>(%done))
~> %slog.(fmt "downloading update for {here}")
@ -1175,6 +1379,7 @@
?: ?=(%| -.p.sign-arvo)
:: ~> %slog.(fmt "download failed into {here}; retrying sync")
:: %- (slog p.p.sign-arvo)
=. ..abet drop
init
::
~> %slog.(fmt "finished downloading update for {here}")
@ -1182,7 +1387,7 @@
:: If nothing changed, just ensure %kids is up-to-date and advance
::
?. (get-remote-diff our syd now [her sud (dec let)])
=< next
=< next:drop
?~ kid
~> %slog.(fmt "remote is identical to {here}, skipping")
..abet
@ -1191,15 +1396,22 @@
..abet
~> %slog.(fmt "remote is identical to {here}, merging into {<u.kid>}")
(merg /kids u.kid)
:: wait for approval if can't automerge & signal available update
::
?. |(=(our her) yea =([~ &] nit) &(=(~ nit) mer))
=. ..abet gain
next
:: Else start merging, but also immediately start listening to
:: the next revision. Now, all errors should no-op -- we're
:: already waiting for the next revision.
::
=. yea |
=. ..abet (merg /main syd)
next
::
%main
%main
?> ?=(%mere +<.sign-arvo)
=< tada
?: ?=(%| -.p.sign-arvo)
=+ "kiln: merge into {here} failed, waiting for next revision"
%- (slog leaf/- p.p.sign-arvo)

View File

@ -0,0 +1,54 @@
/- neo
|%
:: ++ dyad (pair @ @)
++ scam
=< scam
|%
++ on ((^on case:neo case:neo) lte)
+$ scam ((mop case:neo case:neo) lte)
--
:: ++ push
++ scum
=< scum
|%
++ on ((^on case:neo (map pith:neo case:neo)) lte)
+$ scum ((mop case:neo (map pith:neo case:neo)) lte)
--
+$ muck
$: =soil
=scum
why=scam
zed=scam
==
++ soil
=< a
^~
|%
++ on ((^on case:neo muck:neo) lte)
+$ a ((mop case:neo muck:neo) lte)
--
++ plot
:: $dirt: Layer 1 of the namespace
++ dirt
|%
+$ card (pair pith note)
+$ note
$% [%grow =pail case=(unit case) =oath]
[%cull ~]
==
+$ loam (axal soil)
+$ gift dust
--
++ frog
[=pail =
++ till
|_ =loam
++ plow
|=
++ grew
|= [=pail:neo case=@ud]
++ grow
|= [=pail:neo

View File

@ -220,10 +220,14 @@
`[pith key.u.lat]
::
++ grow
|= [=pail:neo cas=(unit case:neo) =oath:neo]
^- (quip loot:neo loam:dirt:neo)
=/ =poem:neo [[(fall cas +(case)) oath] `pail]
(make poem)
|= [=pith:neo =pail:neo cas=(unit case:neo) =oath:neo]
^- (quip dust:neo loam:dirt:neo)
=/ l=loam:dirt:neo (~(dip of:neo loam) pith)
=/ =poem:neo [[(fall cas +(~(case plow l))) oath] `pail]
=^ loot=(list loot:neo) l
(~(make plow l) poem)
:- (turn loot (lead pith))
(~(rep of:neo loam) pith l)
++ make
|= =poem:neo
^- (quip loot:neo loam:dirt:neo)
@ -244,7 +248,7 @@
%add
%dif
?: &(=(%dif mode) =(q.q:(need old) q.poem))
~& %dupe-skipping
:: ~& %dupe-skipping
`loam
~| overwrite-soil/p.p.poem
?> !(has:on:soil:neo u.fil.loam p.p.poem)
@ -252,22 +256,36 @@
loam(fil `(put:on:soil:neo u.fil.loam [p.p .]:poem))
::
++ cull
?~ fil.loam
[~ loam]
(make [[+(case) *oath:neo] ~])
=| =grit:neo
|= =pith:neo
^+ [grit loam]
=/ [loot=(list loot:neo) l=loam:dirt:neo]
=/ lom (~(dip of:neo loam) pith)
?: =(~ fil.lom)
`lom
(~(make plow lom) [[+(~(case plow lom)) *oath:neo] ~])
=. grit (welp grit (turn loot (lead pith)))
=/ kids ~(tap by kid.l)
|-
?~ kids
=. loam (~(rep of:neo loam) pith l)
[grit loam]
=/ [iot=iota lo=loam:dirt:neo] i.kids
=/ pit=pith:neo (snoc pith iot)
=/ [loot=(list loot:neo) lom=loam:dirt:neo]
(make(loam lo) [[+(case) *oath:neo] ~])
=. grit (welp grit (turn loot (lead pit)))
=. kid.l (~(put by kid.l) iot lom)
$(kids t.kids) ::
::
++ call
|= =card:dirt:neo
^- (quip gift:dirt:neo _loam)
=/ lom (~(dip of:neo loam) p.card)
%- (trace "call" (print-card card))
=^ gifts=(list loot:neo) lom
?- -.q.card
%grow (~(grow plow lom) +.q.card)
%cull ~(cull plow lom)
==
:_ (~(rep of:neo loam) p.card lom)
(turn gifts |=(loot:neo `gift:dirt:neo`[p.card +<]))
?- -.q.card
%grow (~(grow plow loam) p.card +.q.card)
%cull (~(cull plow loam) p.card)
==
::
++ look
|= =pith:neo
@ -395,7 +413,9 @@
=| gifts=(list gift:dirt:neo)
|= =epic:neo
^+ [gifts loam farm]
=/ pic ~(tap of:neo epic)
=/ pic
%+ sort ~(tap of:neo epic)
|=([[a=pith:neo *] [b=pith:neo *]] (lte-pith:neo a b))
|-
?~ pic
:+ gifts loam
@ -453,11 +473,64 @@
~
`[pit %dif]
==
++ fallback-peek-kids
|= [=care:neo =pith:neo fam=farm:neo]
^- (list pith:neo)
?: ?=(%x care) ~
?^ fil.fam
=/ [=land:neo =plot:neo] u.fil.fam
?~ kid=(ram:on:plan:neo ?:(?=(%y care) by-kids-mut.plot by-desc-mut.plot))
~
~(tap in val.u.kid)
%- zing
%+ turn ~(tap by kid.fam)
|= [=iota f=farm:neo]
^- (list pith:neo)
(fallback-peek-kids care (snoc pith iota) f)
++ fallback-peek-x
|= =pith:neo
^- (unit saga:neo)
=/ sol (~(gut of:neo loam) pith *soil:neo)
?~ val=(ram:on:soil:neo sol)
~
?~ q.val.u.val
~
`[*aeon:neo u.q.val.u.val]
::
++ fallback-peek
|= [=care:neo =pith:neo]
^- (axal:neo saga:neo)
%- gas
%+ welp
?~ rot=(fallback-peek-x pith)
~
[pith u.rot]^~
%+ murn (fallback-peek-kids care pith (~(dip of:neo farm) pith))
|= p=pith:neo
^- (unit [pith:neo saga:neo])
=/ kid (welp pith p)
?~ sag=(fallback-peek-x kid)
~
`[kid u.sag]
++ piek
|= [=care:neo =pith:neo]
^- (unit (unit (axal:neo saga:neo)))
:: ~& peek-no-once/[care pith]
``(fallback-peek care pith)
::
++ peek
|= [=care:neo =pith:neo]
^- (unit (unit (axal:neo saga:neo)))
?~ nce=(now-once care pith)
:: ~& peek-no-once/[care pith]
~
:: =/ res
:: (fallback-peek care pith)
:: ~? !=(res [~ ~])
:: res/res
:: ``(fallback-peek care pith)
(look care u.nce pith)
++ look-x
|= [=case:neo =pith:neo]
@ -467,6 +540,14 @@
?: ?=($@(~ [~ ~]) res)
res
`(~(get of:neo u.u.res) ~)
++ gas
=| axe=(axal:neo saga:neo)
|= res=(list (pair pith:neo saga:neo))
^+ axe
?~ res
axe
$(axe (~(put of:neo axe) p.i.res q.i.res), res t.res)
::
++ look
|= [=care:neo =once:neo =pith:neo]
@ -485,12 +566,6 @@
%y read-y
%z read-z
==
++ gas
|= res=(list (pair pith:neo saga:neo))
^+ axe
?~ res
axe
$(axe (~(put of:neo axe) p.i.res q.i.res), res t.res)
++ read-x (gas read-x-raw)
++ read-x-raw
^- (list (pair pith:neo saga:neo))

View File

@ -213,7 +213,7 @@
=< q
%- need %- need
%- scry:(ames-gate now eny roof)
[~ / %x [[our %$ da+now] /peers/(scot %p her)]]
[[~ ~] / %x [[our %$ da+now] /peers/(scot %p her)]]
::
++ gall-scry-nonce
|= $: =gall-gate
@ -227,7 +227,7 @@
=< q
%- need %- need
%- scry:(gall-gate now eny roof)
[~ / %n [[our dude da+now] [%$ (scot %p ship.sub) [term wire]:sub]]]
[[~ ~] / %n [[our dude da+now] [%$ (scot %p ship.sub) [term wire]:sub]]]
::
++ load-agent
|= [=ship =gall-gate =dude:gall =agent:gall]

1
pkg/arvo/lib/verb Symbolic link
View File

@ -0,0 +1 @@
../../base-dev/lib/verb

View File

@ -0,0 +1 @@
../../../base-dev/mar/kiln/approve-merge.hoon

View File

@ -0,0 +1 @@
../../../base-dev/mar/kiln/jump-ask.hoon

View File

@ -0,0 +1 @@
../../../base-dev/mar/kiln/jump-opt.hoon

1
pkg/arvo/mar/kiln/jump.hoon Symbolic link
View File

@ -0,0 +1 @@
../../../base-dev/mar/kiln/jump.hoon

View File

@ -0,0 +1 @@
../../../base-dev/mar/kiln/sync-update.hoon

View File

@ -1,20 +1,18 @@
::
:::: /hoon/md/mar
:::: /hoon/atom/mar
::
/? 310
::
=, format
:::: A minimal atom mark
::
=, mimes:html
|_ txt=wain
::
++ grab :: convert from
|%
++ mime |=((pair mite octs) (to-wain q.q))
++ noun wain :: clam from %noun
--
++ grow
|%
++ mime [/text/plain (as-octs (of-wain txt))]
--
|_ ato=@
++ grab |%
++ noun @
++ mime |=([* p=octs] q.p)
--
++ grow |%
++ mime [/application/x-urb-unknown (as-octs ato)]
--
++ grad %mime
--

View File

@ -1,14 +1,14 @@
::
/- neo
|_ =slip:neo
|_ =ack:neo
::
++ grad %noun
++ grab :: convert from
|%
++ noun slip:neo
++ noun ack:neo
--
++ grow
|%
++ noun slip
++ noun ack
--
--

View File

@ -1 +0,0 @@
../../base-dev/mar/txt.hoon

18
pkg/arvo/mar/txt.hoon Normal file
View File

@ -0,0 +1,18 @@
::
:::: /hoon/atom/mar
::
/? 310
::
:::: A minimal atom mark
::
=, mimes:html
|_ ato=@
++ grab |%
++ noun @
++ mime |=([* p=octs] q.p)
--
++ grow |%
++ mime [/application/x-urb-unknown (as-octs ato)]
--
++ grad %mime
--

1
pkg/arvo/mar/verb Symbolic link
View File

@ -0,0 +1 @@
../../base-dev/mar/verb

View File

@ -1,168 +1,232 @@
/@ accel-conf
/@ htmx
/- feather-icons
:- [%accel-conf %$ %htmx]
|= conf=accel-conf
|= =bowl:neo
|^ ^- manx
|^
;div.accel-cell.wf.hf.fc
;+ controls
;div.tabs.fc.grow.scroll-y
;div.tab.in.p2.fc.grow
=morph-retain "class"
;+ in
==
;div.tab.ref.p2.hidden.hf
=morph-retain "class"
;+ ref
==
;div.tab.poke.p2.hidden.hf
=morph-retain "class"
;+ poke
==
==
;+ script
==
::
;div.fc.trans-root.grow
++ out-path (snoc (snip here.bowl) %out)
++ print-iota
|= =iota
?@(iota (trip iota) (scow iota))
++ controls
;div.p-1.fr.g2.ac.je
;button.p-1.br1.bd1.b1.hover.toggled
=onclick "accelSwitchTab(this, 'in');"
=morph-retain "class"
; in
==
;button.p-1.br1.bd1.b1.hover
=onclick "accelSwitchTab(this, 'ref');"
=morph-retain "class"
; ref
==
;button.p-1.br1.bd1.b1.hover
=onclick "accelSwitchTab(this, 'poke');"
=morph-retain "class"
; poke
==
==
++ in
;form.fc.grow
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=hoon"
=hx-trigger "input changed delay:0.4s from:[name='text'], input changed delay:0.4s from:[name='a']"
=hx-trigger "input changed delay:0.4s from:find textarea"
=hx-on-htmx-after-request "$(this).closest('.top').find('.refresher').emit('accel-refresh');"
=hx-swap "none"
=hx-target "#code-spinner .loading"
=hx-target-400 "#error-code-{id}"
=hx-indicator "#code-spinner"
=row (scow %ud +:x)
=col (scow %ud +:y)
;div.fc.border.grow.basis-half.wf
;+ code-input
;+ (spinner "code")
;textarea
=class "p2 bd1 br1 scroll-x pre mono wf grow"
=name "text"
=placeholder "code"
=spellcheck "false"
=autocomplete "off"
=value (trip hoon.conf)
=oninput "this.setAttribute('value', this.value);"
; {(trip hoon.conf)}
==
==
;div.fc.border
;+ conf-header
;div.fr
;div.fc.p2
;form.fr.js.hf
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=add-poke"
=hx-swap "none"
=hx-indicator "#code-spinner"
=hx-target "#code-spinner .loading"
;input
=type "text"
=placeholder "/{(scow %p our.bowl)}/shrub/to/poke"
=name "pith"
=autocomplete "off"
=oninput "this.setAttribute('value', this.value);"
;
==
;input
=type "text"
=placeholder "%some-type"
=name "stud"
=autocomplete "off"
=oninput "this.setAttribute('value', this.value);"
;
==
;button
=type "submit"
; Route poke
==
==
==
;div.fc.p2
::
;+ deps
;form.fr.js.hf
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=add-dep"
=hx-swap "none"
=hx-target "#conf-spinner .loading"
=hx-target-400 "#error-add-{id}"
=hx-indicator "#conf-spinner"
=row (scow %ud +:x)
=col (scow %ud +:y)
;div.fc.grow.basis-half.wf
;div.fr
;input
=type "text"
=placeholder "name"
=autocomplete "off"
=oninput "this.setAttribute('value', this.value);"
=name "name"
;
==
;input
=type "text"
=placeholder "/{(scow %p our.bowl)}/demo/cell/5"
=autocomplete "off"
=oninput "this.setAttribute('value', this.value);"
=name "pith"
;
==
==
;button
=type "submit"
; Add dep
==
;+ (spinner "conf")
==
==
==
==
++ poke
;div
;+ add-poke
;+ pokes
==
==
++ id
^- tape
%- zing
%+ turn (pout (tail here.bowl))
|= smeg=@ta
%+ weld "--"
(trip smeg)
::
++ x (rear (snip (snip here.bowl)))
++ y (rear (snip here.bowl))
++ spinner
|= =tape
;div.b1.loader.p1.s-2.f2
=id (welp tape "-spinner")
;span.loaded: saved
;span.loading: ---
==
++ conf-header
=/ pit=tape (en-tape:pith:neo (snoc (snip here.bowl) %out))
;div.b1.border.fr.jb
;span.p1.mono.s-1: {pit}
;button.br1.border.b1.hover
=style "padding: 4px 8px;"
=type "button"
=pith pit
=onclick "navigator.clipboard.writeText(this.getAttribute('pith'));"
; copy path
==
==
++ code-input
;textarea#input.wf.p2.pre.mono.grow
=name "text"
=placeholder "code"
=spellcheck "false"
=value (trip hoon.conf)
=oninput "this.setAttribute('value', this.value);"
; {(trip hoon.conf)}
++ ref
;div
;+ add-dep
;+ deps
==
++ x (rear `pith`(snip (snip here.bowl)))
++ y (rear `pith`(snip here.bowl))
++ deps
^- manx
?: =(~ crew.conf)
;div.fr: No dependencies
;div.fc
;div.s1.p1: Dependencies
;div.fr.p2.f3: no deps
;div.fc.g2.p2.scroll-y
;*
%+ turn ~(tap by crew.conf)
|= [=term =pith:neo]
=/ tap (trip term)
;label.fr.p1
;div.border.wf
=name "name"
;div.fr.g3.ac
=id "conf-dep-{tap}"
;button.bd1.br1.p-1.b1.hover.loader
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=del-dep"
=hx-swap "outerHTML"
=hx-target "find .loading"
=tap tap
;span.loaded: delete
;span.loading
;+ loading.feather-icons
==
==
;div.f3
; {tap}
==
;label.border.wf: {(en-tape:pith:neo pith)}
;button.border
=hx-post "/neo/sky{(en-tape:pith:neo here.bowl)}?stud=del-dep"
; Delete
;label.hidden
=name "name"
=value tap
;
==
;div.mono: {(en-tape:pith:neo pith)}
==
==
++ add-dep
;form.frw.js.g2
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=add-dep"
=hx-swap "outerHTML"
=hx-target "find .loading"
=hx-on-htmx-after-request "$(this).find('input').attr('value', '');this.reset();"
=row (scow %ud +:x)
=col (scow %ud +:y)
;input.p-1.br1.bd1.fr.wfc
=style "min-width: 0; flex: 1;"
=type "text"
=placeholder "name"
=autocomplete "off"
=oninput "this.setAttribute('value', this.value);"
=morph-no-swap ""
=name "name"
=required ""
;
==
;input.p-1.br1.bd1.fr.grow.wfc
=style "min-width: 0; flex: 1;"
=type "text"
=placeholder "/{(scow %p our.bowl)}/demo/cell/5"
=autocomplete "off"
=oninput "this.setAttribute('value', this.value);"
=required ""
=morph-no-swap ""
=name "pith"
;
==
;button.br1.bd1.b1.hover.p-1.loader
=type "submit"
;span.loaded: add dep
;span.loading
;+ loading.feather-icons
==
==
==
++ error
|= =tang
;div.pre.mono.p2
++ pokes
^- manx
?: =(0 ~(wyt by poke.conf))
;div.fr.p2.f3: no pokes
;div.fc.g2.p2.scroll-y
;*
%+ turn (scag 25 tang)
|= =tank
;span: {(of-wall:format (~(win re tank) 0 80))}
%+ turn ~(tap by poke.conf)
|= [=pith:neo =stud:neo]
=/ pit (en-tape:pith:neo pith)
=/ tap (en-tape:pith:neo (snip (snip (snip here.bowl))))
;div.frw.g2.ac
;button.bd1.br1.p-1.b1.hover.loader
=hx-post "/neo/hawk{tap}?stud=send-poke"
=hx-swap "none"
=pith pit
=x (scow %ud +:x)
=y (scow %ud +:y)
;span.loaded: send
;span.loading
;+ loading.feather-icons
==
==
;button.bd1.br1.p-1.b1.hover.loader
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=del-poke"
=hx-swap "outerHTML"
=hx-target "find .loading"
=pith pit
;span.loaded: delete
;span.loading
;+ loading.feather-icons
==
==
;div.f3
; {<stud>}
==
;div.mono: {pit}
==
==
++ add-poke
;form.fr.js.hf.g2
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=add-poke"
=hx-swap "outerHTML"
=hx-target "find .loading"
=hx-on-htmx-after-request "$(this).find('input').attr('value', '');this.reset();"
=row (scow %ud +:x)
=col (scow %ud +:y)
;input.p-1.br1.bd1
=style "min-width: 0; flex: 1;"
=type "text"
=placeholder "%stud"
=autocomplete "off"
=oninput "this.setAttribute('value', this.value);"
=morph-no-swap ""
=name "stud"
=required ""
;
==
;input.p-1.br1.bd1.grow
=style "min-width: 0; flex: 1;"
=type "text"
=placeholder "/{(scow %p our.bowl)}/poke/target"
=autocomplete "off"
=oninput "this.setAttribute('value', this.value);"
=required ""
=morph-no-swap ""
=name "pith"
;
==
;button.br1.bd1.b1.hover.p-1.loader
=type "submit"
;span.loaded: add poke
;span.loading
;+ loading.feather-icons
==
==
==
++ script
;script
;+ ;/ %- trip
'''
function accelSwitchTab(el, name) {
$(el).siblings().removeClass('toggled');
$(el).addClass('toggled');
let tabs = $(el).closest('.accel-cell').find('.tab');
tabs.addClass('hidden');
tabs.filter('.'+name).removeClass('hidden');
}
'''
==
--

View File

@ -2,118 +2,219 @@
/@ accel-cell
/@ accel-conf
/@ htmx
/- feather-icons
:- [%accel %$ %htmx]
|= =accel
|= =bowl:neo
|^
;div.accel-top.wf.hf
;div.top.wf.hf
=style
"""
display: grid;
grid-template-rows: 1fr 200px;
grid-template-columns: 1fr;
overflow: hidden;
grid-template-areas:
"table"
"dashboards";
"""
;+ table
;+ dashboard-stub
;+ refresher
;+ style
;+ dashboards
;+ script
==
++ id
^- tape
%- zing
%+ turn (pout (tail here.bowl))
|= smeg=@ta
%+ weld "--"
(trip smeg)
::
++ table
;div
;table
=id "table-{id}"
;tbody
;tr
;th.mono;
;*
%+ turn (gulf 1 10)
|= n=@
;th.mono.f2.tc: {<n>}
==
;*
%+ turn (gulf 1 10)
|= x=@
;tr
;td.mono.tr.f2(style "width: 1px;"): {<x>}
;*
%+ turn (gulf 1 10)
|= y=@
^- manx
=/ pax=pith:neo #/[ud/x]/[ud/y]
=/ kid=bowl:neo bowl
=. kids.kid [~ ~]
=. here.kid :(welp here.bowl pax)
=. deps.kid ~
=/ in=manx
?~ res=(~(get of:neo kids.bowl) (snoc pax %in))
*manx
=. here.kid (snoc here.kid %in)
(!<(htmx q.pail.u.res) kid)
=/ out=manx
?~ res=(~(get of:neo kids.bowl) (snoc pax %out))
*manx
=. here.kid (snoc here.kid %out)
(!<(htmx q.pail.u.res) kid)
;td.border
;+
;button.b1.scroll-none.hover.cell-btn.p2.wf.hf
=id "cell-{id}-{<x>}-{<y>}"
=hx-get "/neo/hawk{(en-tape:pith:neo here.bowl)}/{<x>}/{<y>}/in"
=hx-target "#dashboard-{id}"
=hx-select ".trans-root"
=hx-swap "innerHTML"
=morph-retain "class"
=onclick "$('.cell-btn').removeClass('toggled');$(this).addClass('toggled');"
;div.mono
;+ out
==
==
==
++ resizer
;form.fr.ac.je.g1.p2
=hx-post "/neo/sky"
=hx-confirm "not yet implemented. bother ~migrev about this"
;input.p-1.br1.bd1.b0
=style "width: 80px;"
=type "number"
=min "2"
=max "20"
=step "1"
=value "10"
;
==
;div: x
;input.p-1.br1.bd1.b0
=style "width: 80px;"
=type "number"
=min "2"
=max "20"
=step "1"
=value "10"
;
==
;button.loader.p-1.br1.bd1.b1.hover
;span.loaded: resize
;span.loading
;+ loading.feather-icons
==
==
==
++ table
;div.scroll-x.scroll-y
=style "grid-area: table"
::;+ resizer
;+ grid
==
++ dashboard-stub
;div.b0.fr
=id "dashboard-{id}"
;
++ rows 5
++ cols 5
++ grid
;div
=style
"""
width: max-content;
display: grid;
grid-template-rows: repeat({<+(rows)>}, auto);
grid-template-columns: repeat({<+(cols)>}, fit-content(220px));
"""
;*
%+ turn (gulf 0 cols)
|= n=@
(index n)
;*
%+ turn (gulf 0 (dec (mul rows +(cols))))
|= i=@
?: =(0 (~(sit fo +(cols)) i))
(index +((div i +(cols))))
=/ d (dvr i +(cols))
=/ x q.d
=/ y +(p.d)
(cell x y)
==
++ out-preview
:: XX cool kids would use a role conversion...
:: YY or maybe cool kids prefer locality of behaviour
:: ZZ SOMEONE EXPLAIN ME POLYMORPHISM NOW!
|= =pail:neo
^- manx
?+ p.pail
;div: bruh, how did that a {<p.pail>} stud even get in here?
%vase
=/ vax q.pail
?: =(->-:vax %n) ;/("") :: empty if cell is null
;/((of-wall:format (~(win re (sell vax)) 0 50)))
::
%tang
;div.f-3: ERROR
==
++ cell
|= [x=@ y=@]
=/ pix=pith:neo #/[ud/x]/[ud/y]/out
;button.p-1.bd1.b0.hover.tl.mono.cell-btn
=morph-retain "class"
=onclick "accelRevealDashboard(this, '{<x>}', '{<y>}')"
=style
"""
min-height: 30px;
max-height: 100px;
min-width: 50px;
max-width: 400px;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
"""
;+
?~ res=(~(get of:neo kids.bowl) pix)
;div.f-2: EMPTY
(out-preview q.saga.u.res)
==
++ index
|= n=@
?: =(0 n) ;div;
;div.fc.jc.ac.b0.p2.f3.s-2: {<n>}
++ dashboards
;div.b0.fc.relative.scroll-x.scroll-y
=style "grid-area: dashboards; border-top: 1px solid var(--f2); max-height: 200px;"
;+ refresher
;*
%+ turn (gulf 0 (dec (mul rows cols)))
|= i=@
=/ d (dvr i cols)
=/ x +(q.d)
=/ y +(p.d)
(dashboard x y)
::
;div.grow.fc.jc.ac.f3
=cell "empty"
=morph-retain "class"
; select a cell
==
==
++ dashboard
|= [x=@ y=@]
;div.grow.frw.af.js.hidden
=style ""
=cell "{<x>}-{<y>}"
=morph-retain "class"
;+ (in x y)
;+ (out x y)
==
++ in
|= [x=@ y=@]
=/ pax=pith:neo #/[ud/x]/[ud/y]
=/ kid=bowl:neo bowl
=. kids.kid [~ ~]
=. here.kid :(welp here.bowl pax /in)
=. deps.kid ~
;div.basis-half.shrink-0.grow.bd1
=style "min-width: 350px; min-height: 150px;"
;+
?~ res=(~(get of:neo kids.bowl) (snoc pax %in))
;div: none - in {<[x y]>}
(!<(htmx q.pail.u.res) kid)
==
++ out
|= [x=@ y=@]
=/ pax=pith:neo #/[ud/x]/[ud/y]/out
=/ kid=bowl:neo bowl
=. kids.kid [~ ~]
=. here.kid (welp here.bowl pax)
=. deps.kid ~
;div.basis-half.shrink-0.bd1.fc.g2.grow
=style "min-width: 350px; min-height: 150px;"
;div.b0.fr.g2.ac.je.p-1.bd1
;div.s-1: {(en-tape:pith:neo (snip pax))}
;button.p-1.b1.hover.br1.bd1.s-2
=onclick "navigator.clipboard.writeText('{(en-tape:pith:neo here.kid)}');"
; copy full path
==
==
;div.p2.pre.grow.mono.scroll-y.scroll-x
;+
?~ res=(~(get of:neo kids.bowl) pax)
;div: none out - {<[x y]>}
(!<(htmx q.pail.u.res) kid)
==
==
++ refresher
;div.hidden
=hx-get "{(en-tape:pith:neo :(weld /neo/hawk here.bowl))}"
=hx-trigger "every 3s"
=hx-target "table"
=hx-select "table"
=hx-swap "morph"
;
;div.absolute
=style "top: 1em; left: 1em;"
;div.loader.refresher
=hx-get "{(en-tape:pith:neo :(weld /neo/hawk here.bowl))}?no-save"
=hx-trigger "every 7s, accel-refresh"
=hx-target "closest .top"
=hx-select ".top"
=hx-swap "morph"
;span.loaded;
;span.loading
;+ loading.feather-icons
==
==
==
++ style
;style.hidden
++ script
;script
;+ ;/ %- trip
'''
table {
border-collapse: collapse;
width: 100%;
grid-area: table;
overflow-y: auto;
}
.scroll-none {
overflow-x: auto;
}
td, tr {
margin: 0;
padding: 0;
height: fit-content;
}
.accel-top {
display: grid;
grid-template-rows: 1fr 1fr;
grid-template-columns: 1fr;
grid-template-areas:
"table"
"dashboard";
function accelRevealDashboard(el, x, y) {
let top = $(el).closest('.top');
top.find('.cell-btn').removeClass('toggled');
$(el).addClass('toggled');
let dashboards = top.find('[cell]');
dashboards.addClass('hidden');
dashboards.filter(`[cell='${x}-${y}']`).removeClass('hidden');
}
'''
==

View File

@ -0,0 +1,13 @@
/@ add-dep
/- feather-icons
:- [%add-dep %$ %htmx]
|= dep=add-dep
|= =bowl:neo
;div.loading
=hx-get "/neo/hawk{(en-tape:pith:neo here.bowl)}?no-save"
=hx-trigger "load"
=hx-swap "morph"
=hx-target "closest .accel-cell"
=hx-select ".accel-cell"
;+ loading.feather-icons
==

View File

@ -0,0 +1,13 @@
/@ add-poke
/- feather-icons
:- [%add-poke %$ %htmx]
|= add=add-poke
|= =bowl:neo
;div.loading
=hx-get "/neo/hawk{(en-tape:pith:neo here.bowl)}?no-save"
=hx-trigger "load"
=hx-swap "morph"
=hx-target "closest .accel-cell"
=hx-select ".accel-cell"
;+ loading.feather-icons
==

View File

@ -0,0 +1,13 @@
/@ del-dep
/- feather-icons
:- [%del-dep %$ %htmx]
|= dep=del-dep
|= =bowl:neo
;div.loading
=hx-get "/neo/hawk{(en-tape:pith:neo here.bowl)}?no-save"
=hx-trigger "load"
=hx-swap "morph"
=hx-target "closest .accel-cell"
=hx-select ".accel-cell"
;+ loading.feather-icons
==

View File

@ -0,0 +1,13 @@
/@ del-poke
/- feather-icons
:- [%del-poke %$ %htmx]
|= del=del-poke
|= =bowl:neo
;div.loading
=hx-get "/neo/hawk{(en-tape:pith:neo here.bowl)}?no-save"
=hx-trigger "load"
=hx-swap "morph"
=hx-target "closest .accel-cell"
=hx-select ".accel-cell"
;+ loading.feather-icons
==

View File

@ -1,14 +1,24 @@
/@ diary
/@ diary :: name=@t
:: import /lib/feather-icons
/- feather-icons
:: declare that this is a conversion from diary to HTMX
:- [%diary %$ %htmx]
:: gate takes a diary and a bowl:neo,
:: so we can access here.bowl
|= dia=diary
|= =bowl:neo
:: returns a manx, in this case HTMX
^- manx
:: construct Sail using helper arms
|^
;div.p2
=label "Diary"
:: <div class="p2" label="Diary">
;div.p-page
:: <div class="ma fc g2 mw-page">
;div.ma.fc.g2.mw-page
:: render the text input for new entries
;+ form-put-entry
:: compose several link-entry elements generated
:: by the +turn gate into one HTMX node
;*
%+ turn
%+ sort
@ -20,18 +30,38 @@
|= [a=[=pith *] b=[=pith *]]
(gth ->.pith.a ->.pith.b)
link-entry
==
==
== :: </div>
== :: </div>
::
:: text input for new entries
++ form-put-entry
::
:: <form
:: class="fc g2"
:: style="margin-bottom: 30px"
:: head="put-entry"
:: hx-post="/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=diary-diff"
:: hx-on-submit="this.reset()"
:: hx-target="find .loading"
:: hx-swap="outerHTML"
:: >
;form.fc.g2
=style "margin-bottom: 30px;"
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=diary-diff"
=style "margin-bottom: 30px;"
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=diary-diff"
=hx-on-submit "this.reset()"
=hx-target "find .loading"
=hx-swap "outerHTML"
=head "put-entry"
;date-now(name "id");
=hx-target "find .loading"
=hx-swap "outerHTML"
=head "put-entry"
:: <date-now name="now" />
;date-now(name "now");
:: <textarea
:: class="p2 bd1 br1"
:: name="text"
:: placeholder="today, i ..."
:: oninput="this.setAttribute('value', this.value)"
:: rows="5"
:: required=""
:: autocomplete=""
:: />
;textarea.p2.bd1.br1
=name "text"
=placeholder "today, i ..."
@ -40,35 +70,55 @@
=required ""
=autocomplete "off"
;
==
== :: </textarea>
:: <button class="p2 b1 br1 bd1 wfc hover loader">
;button.p2.b1.br1.bd1.wfc.hover.loader
:: <span class="loaded s2">create</span>
;span.loaded.s2: create
:: <span>
;span.loading
;+ loading.feather-icons
==
==
==
::
== :: </span>
== :: </button>
== :: </form>
::
:: entry box
++ link-entry
::
|= [pax=pith =idea:neo]
=/ tape (trip !<(@t q.pail.idea))
:: make sure the pail does in fact contain %txt
?. =(%txt p.q.saga.idea)
;div: error
:: extract information from the given pith and pail:neo
=/ tape (trip !<(@t q.q.saga.idea))
=/ subject-end (fall (find [10]~ tape) 56)
=/ subject (scag subject-end tape)
=/ id (trip (snag 0 (pout pax)))
:: <div class="fr g2">
;div.fr.g2
:: <a
:: class="p2 br1 grow b1 hover loader"
:: href="{(en-tape:pith:neo (weld /neo/hawk here.bowl))}/{id}"
:: >
;a.p2.br1.grow.b1.hover.loader
=href "{(en-tape:pith:neo (weld /neo/hawk here.bowl))}/{id}"
:: <div class="loaded fc g1 js as g2">
;div.loaded.fc.g1.js.as.g2
:: <span class="f3">{YYYY-MM-DD}</span>
;span.f3: {(pretty-date `@da`->:pax)}
:: <span class="bold">{subject}</span>
;span.bold: {subject}
==
== :: </div>
:: <span class="loading">
;span.loading
;+ loading.feather-icons
==
==
== :: </span>
== :: </a>
;button.p2.br1.fr.g2.b1.hover.fc.ac.jc.loader
=onclick "alert('not yet implemented. no tombstoning?')"
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=diary-diff"
=head "del-entry"
=hx-target "find .loading"
=hx-swap "outerHTML"
=diary-id id
;span.loaded
;+ close.feather-icons
==
@ -77,6 +127,8 @@
==
==
==
::
:: styled date string
++ pretty-date
|= date=@da
^- tape

View File

@ -0,0 +1,25 @@
/@ message
/- feather-icons
/- messages
:- [%ship %$ %htmx]
|= =ship
|= =bowl:neo
^- manx
;div.p2.wf.hf
=hx-on-message-sent "$(this).find('.refresher').emit('refresh');"
;div.mw-page.ma.wf.hf
=style
"""
display: grid;
grid-template-rows: 1fr auto;
grid-template-columns: auto;
grid-template-areas:
"messages"
"sender"
"""
;+ script:messages
;+ style:messages
;+ (render-messages:messages bowl)
;+ (render-sender:messages [bowl /pub])
==
==

View File

@ -44,8 +44,11 @@
%diary
%iframe
%task
%todos
%txt
%hoon
%sail
%slideshow
==
|= t=@tas
^- manx
@ -71,7 +74,9 @@
%+ turn folder
|= name=@tas
=/ pith ~[name]
=/ =pail:neo pail:(~(got of:neo kids.bowl) pith)
?~ suga=(~(get of:neo kids.bowl) pith)
;div.p3.br1.bd1: no file called {<name>}
=/ =pail:neo q.saga.u.suga
=/ =stud:neo p.pail
=/ =path (pout (welp here.bowl pith))
=/ label

View File

@ -0,0 +1,21 @@
/@ message
/@ groupchat
/- feather-icons
/- messages
:- [%groupchat %$ %htmx]
|= =groupchat
|= =bowl:neo
^- manx
;div.p2
=label "Chat"
;+ script:messages
;+ style:messages
;div.ma.fe.g2.wf
=style "max-width: 650px;"
;div.fc.g2.wf
=id "children"
;+ (render-messages:messages bowl)
==
;+ (render-sender:messages [bowl /pub])
==
==

View File

@ -55,22 +55,29 @@
=hx-indicator "#indicator-{id}"
;*
=/ apps
%+ welp apps.home
%+ welp
apps.home
%- skip
:_ |= =pith
(gth (lent pith) 1)
%~ tap in
(~(dif in ~(key by ~(tar of:neo kids.bowl))) `(set pith)`(silt apps.home))
%- %~ dif in
%~ key by
%~ tar of:neo
kids.bowl
`(set pith)`(silt apps.home)
%+ murn apps
|= =pith
^- (unit manx)
?~ pith ~
=/ =path (pout (welp here.bowl pith))
?: (gth (lent path) 3) ~ :: /our/home/whatever
:- ~
;div.relative.br2
=pith (en-tape:pith:neo pith)
;a.b1.br2.block.fc.as.js.hover.p3.s1.border-2.loader
=style "width: 160px; height: 160px;"
=hx-target "closest .hawk"
=hx-swap "innerHTML"
=hx-swap "outerHTML"
=hx-indicator "this"
=href (trip (spat ['neo' 'hawk' path]))
;span.loaded: {(trip (snag 0 (pout:neo pith)))}
@ -86,24 +93,24 @@
; • • •
==
;div.fr.hidden.g1.p1
;button.grow.tc.s-2.b0.br2.p1
;button.grow.fr.jc.b0.br2.p1
=hx-trigger "click"
=onclick "let t = $(this).closest('.relative'); t.parent().prepend(t);"
; |<
;+ chevron-first.feather-icons
==
;button.grow.tc.s-2.b0.br2.p1
;button.grow.fr.jc.b0.br2.p1
=onclick "let t = $(this).closest('.relative'); t.insertBefore(t.prev());"
=hx-trigger "click"
; <
;+ chevron-left.feather-icons
==
;button.grow.tc.s-2.b0.br2.p1
;button.grow.fr.jc.b0.br2.p1
=hx-trigger "click"
=onclick "let t = $(this).closest('.relative'); t.insertAfter(t.next());"
; >
;+ chevron-right.feather-icons
==
;button.grow.tc.s-2.b0.br2.p1
;button.grow.fr.jc.b0.br2.p1
=onclick "let t = $(this).closest('.relative'); t.parent().append(t);"
; >|
;+ chevron-last.feather-icons
==
==
==

View File

@ -1,10 +1,13 @@
/@ htmx
/- feather-icons
:- [%hoon %$ %htmx]
|= hon=@t
|= =bowl:neo
=/ =name:neo
[our here]:bowl
=/ =file:ford:neo
=/ fool=(each file:ford:neo tang)
%- mule
|.
(scan (trip hon) (rein:ford:neo name))
=/ src=wain
(to-wain:format hon)
@ -23,57 +26,77 @@
::
++ apex
^- manx
;div.wf.hf.p3.hoon.fc.g2
;+ imports
;+ contents
;+ style
==
++ imports
;div.frw.g2
;*
%+ turn pro.file
|= =pro:ford:neo
^- manx
;a.p2.br1.b1.hover.s-1
=href (spud (post-href %pro stud.pro))
=hx-target "closest .hawk"
=hx-swap "innerHTML"
; {<stud.pro>}
==
==
++ error
;details.error-parent.wf.br1.bd1(open "")
=style "max-height: 220px;"
;summary.p2.br1.b-3.f-3: error
;div.p3
;div.error.empty;
==
==
++ contents
;form.fc.g2.wf.relative.grow.scroll-y
;form.wf.hf.hoon.fc
=hx-put "{(en-tape:pith:neo (welp /neo/hawk here.bowl))}?stud=hoon"
=hx-trigger "click from:find button, keydown[metaKey&&key=='Enter']"
=hx-target "closest .hoon"
=hx-target-error "find .error"
=hx-swap "morph"
;div.relative.grow.fc
;div.wfc.z1.absolute
=style "top: 15px; right: 15px;"
;button.p2.br2.b1.bd1.hover.loader
;span.loaded.fr.ac.g1
;span.bold: save
;span.f2.s-2: cmd+enter
==
;span.loading: ...
;+ imports
;+ contents
;+ script
==
++ imports
;div.frw.jb.ac.g2.p1
;+
?- -.fool
%.n
(error p.fool)
::
%.y
;div.frw.g2.p1.wfc
;*
%+ turn pro.p.fool
|= =pro:ford:neo
^- manx
;a.p2.br1.bd1.b1.hover.s-1
=href (spud (post-href %pro stud.pro))
=hx-target "closest .hawk"
=hx-swap "innerHTML"
; {<stud.pro>}
==
==
;textarea.p2.bd1.br1.scroll-x.pre.mono.wf.grow
=style "outline:none;"
==
;+ saver
==
++ error
|= =tang
=/ =wall (zing (turn tang |=(t=tank (~(win re t) [0 80]))))
=/ =tape (zing (join "\0a" wall))
;details.wf.br1.bd1(open "")
=style "max-height: 220px;"
;summary.p2.br1.b-3.f0: error
;div.pre.mono.p2.wf.scroll-y
;+ ;/ tape
==
==
++ saver
;button.p2.br2.b1.bd1.hover.loader
;span.loaded.fr.ac.g1
;span.bold: save
;span.f2.s-2: cmd+enter
==
;span.loading
;+ loading.feather-icons
==
==
++ contents
;div.fc.g2.wf.relative.grow.scroll-y
=style "border-top: 1px solid var(--b3); padding-bottom: 250px;"
;div.fr.grow
;div.hf.f3.numbered
=style "min-width: 10px; padding: 8px 5px; border-right: 1px solid var(--b3);"
;*
%+ turn (gulf 1 (lent src))
|= n=@
;div.mono(style "line-height: 1.1;");
==
;textarea.p2.pre.mono.wf.grow.scroll-hidden
=style "outline:none; line-height: 1.1;"
=autocomplete "off"
=rows "1"
=spellcheck "false"
=name "text"
=oninput "this.setAttribute('value', this.value);"
=oninput "this.setAttribute('value', this.value); skyHoonRenumber(this);"
=value (trip hon)
;*
%+ turn src
@ -81,14 +104,27 @@
;/ "{(trip lin)}\0a"
==
==
;+ error
==
++ style
;style
++ script
;script
;+ ;/ %- trip
'''
details.error-parent:has(.error:empty) {
display: none;
function skyHoonRenumber(el) {
// corrects the line numbers
let lines = 1 + el.value.split('\n').length;
let nums = $(el).parent().find('.numbered').get()[0];
let kids = nums.children.length + 1;
while (kids < lines) {
let div = document.createElement('div');
div.classList.add('mono');
div.style = 'line-height: 1.1;';
nums.appendChild(div);
kids = kids + 1;
}
while (kids > lines) {
nums.children[0].remove();
kids = kids - 1;
}
}
'''
==

View File

@ -0,0 +1,23 @@
/@ sky-diff
/@ http-request
/- serv=sky-server
:- [%http-request %$ %sky-diff]
|= =http-request
=/ pam (~(uni by pam:(parse-url:serv http-request)) (parse-form-body:serv http-request))
=/ bod ~(. by pam)
=/ head (@tas (got:bod 'head'))
^- sky-diff
?+ head ~|(bad-head/head !!)
%new-hawk
[head (slav %da (got:bod 'now'))]
%close
[head (slav %ud (got:bod 'slot'))]
%maximize
[head (slav %ud (got:bod 'slot'))]
%slide-up
[head (slav %ud (got:bod 'slot'))]
%slide-down
[head (slav %ud (got:bod 'slot'))]
%minimize
[head (slav %ud (got:bod 'slot'))]
==

View File

@ -0,0 +1,136 @@
/@ groupchat
/@ ship
/- feather-icons
/- messages
:- [%messenger %$ %htmx]
|= ~
|= =bowl:neo
|^
view
::
++ view
^- manx
;div.p2.fc.ac.view.g2.ma
;style: {style}
;+ make-chat
;+ all-chats
==
::
++ pith-tape
|= =pith
^- tape
(en-tape:pith:neo pith)
::
++ style
^~
%- trip
'''
.view {
max-width: 650px;
padding-bottom: 50vh;
padding-top: 30px;
}
input[type="text"]:hover {
cursor: text;
}
input:focus {
outline: none;
}
.w70{
width: 70%;
}
.bc1{
border: solid 1px var(--b2);
}
'''
::
++ make-chat
=/ oninput
"""
this.setAttribute("value", this.value); this.nextElementSibling.nextElementSibling.setAttribute('hx-get', '/neo/hawk{(pith-tape here.bowl)}/dms/' + this.value); htmx.process(document.body);
"""
;form.fr.jc.g1.w70
=hx-post "/neo/hawk{(pith-tape here.bowl)}?stud=messenger-diff"
=head "new-dm"
=hx-swap "none"
=hx-on-htmx-after-request "$(this).find('.redirect').emit('messenger-created')"
;input.grow.bc1.p2.br1
=name "invites"
=type "text"
=required ""
=placeholder "Start chat (e.g. ~sampel-palnet)"
=oninput oninput
=autocomplete "off"
;
==
;input.hidden.grow.bc1.p2.br1
=type "text"
=name "name"
=placeholder "chat name"
=oninput (trip 'this.setAttribute("value", this.value);')
;
==
;div.redirect.hidden
=hx-target "closest .hawk"
=hx-swap "outerHTML"
=hx-trigger "messenger-created"
;
==
;button.loader.br1.hover.p2.b0.bc1
;span.loaded; >
;span.loading
;+ loading.feather-icons
==
==
==
::
++ all-chats
=/ kids
%+ skid ~(tap of:neo kids.bowl)
|= [=pith =idea:neo]
=(p.q.saga.idea %groupchat)
^- manx
;div.fc.as.g1.w70
;div.fc.ac.g1.wf
;* %+ turn q.kids
|= [=pith =idea:neo]
?~ pith
;span.hidden: no dms
?: (lte 3 (lent pith))
;h1.hidden: {<pith>}
=/ ship
?. ?=([%p @p] (rear pith)) *@p
+:;;([%p @p] (rear pith))
^- manx
;a.br1.hover.b0.fr.jb.wf.bc1
=href "/neo/hawk{(pith-tape here.bowl)}{(pith-tape pith)}"
=hx-swap "innerHTML"
;h3.s-1.p2: {<ship>}
==
==
;div.fc.ac.g1.wf
;* %+ turn p.kids
|= [=pith =idea:neo]
?~ pith ;span.hidden: no groupchats
(chat pith idea)
==
==
::
++ chat
|= [=pith =idea:neo]
=/ org=@p +:;;([%p @p] (snag 1 `(list iota)`pith))
=/ members=(list ship) ~(tap in members:!<(groupchat q.q.saga.idea))
=/ chat +:;;([%t @t] (rear pith))
^- manx
;div.wf.br1.fc.g1
;div.fr.g1
;a.br1.hover.fr.jb.g2.wf.bc1.b0
=href "/neo/hawk{(pith-tape here.bowl)}{(pith-tape pith)}"
=hx-swap "innerHTML"
;h3.s-1.p2: {(trip chat)}
;h3.s-1.p2: {<org>}
==
==
==
--

View File

@ -10,4 +10,3 @@
=/ name=term (vol:mu "name")
?> ((sane %tas) name)
[name pith]

View File

@ -3,10 +3,7 @@
/- manx-utils
:- [%node %$ %add-poke]
|= nod=node
~& nod/nod
^- add-poke
=/ mu ~(. manx-utils nod)
:- (pave:neo (rash (vol:mu "pith") stap))
`@tas`(rash (vol:mu "stud") ;~(pfix cen sym)) :: XX fix

View File

@ -0,0 +1,21 @@
/@ node :: manx
/@ counter-diff :: [%inc ~]
:: import /lib/manx-utils, which helps us work with XML
/- manx-utils
:: declare this is a conversion from node to counter-diff
:- [%node %$ %counter-diff]
|= =node
^- counter-diff
:: initiate the manx-utils door with node
=/ mu ~(. manx-utils node)
::
:: got:mu gets an attribute from the manx by its name
:: in this case, the =head specified in /con/number-htmx
:: we expect the head from the manx to be %inc,
:: but we could add more terms to that type union...
=/ head (?(%inc) (got:mu %head))
::
:: return the [%inc ~] poke
:: if we wanted to handle multiple pokes,
:: we'd switch on the type of head here
[head ~]

View File

@ -5,7 +5,6 @@
|= nod=node
^- del-dep
=/ mu ~(. manx-utils nod)
=/ name=term (vol:mu "name")
=/ name=term (got:mu %tap)
?> ((sane %tas) name)
name

View File

@ -5,5 +5,4 @@
|= nod=node
^- del-poke
=/ mu ~(. manx-utils nod)
(pave:neo (rash (vol:mu "pith") stap))
(pave:neo (rash (got:mu %pith) stap))

View File

@ -1,11 +1,22 @@
/@ node
/@ diary-diff
/@ node :: manx
/@ diary-diff :: ?([%put-entry id=@da txt=@t] [%del-entry id=@da])
:: import /lib/manx-utils
/- manx-utils
:: declare that this is a conversion from a
:: dynamic XML node to diary-diff
:- [%node %$ %diary-diff]
|= nod=node
^- diary-diff
=/ mu ~(. manx-utils nod)
:: extract head, id, and text atttributes from XML node
=/ head (@tas (got:mu %head))
=/ id (slav %da (vol:mu "id"))
=/ text (vol:mu "text")
[%put-entry id text]
?+ head !!
%put-entry
=/ id (slav %da (vol:mu "now"))
=/ text (vol:mu "text")
:: construct the diary-diff
[%put-entry id text]
::
%del-entry
[%del-entry (slav %da (got:mu %diary-id))]
==

View File

@ -0,0 +1,17 @@
/@ node
/@ groupchat-diff
/- manx-utils
:- [%node %$ %groupchat-diff]
|= nod=node
^- groupchat-diff
=/ mu ~(. manx-utils nod)
=/ head (@tas (got:mu %head))
~& >>> head
%- groupchat-diff
?+ head
~| [%unknown-head head]
!!
%remove
=/ =ship `@p`(slav %p (got:mu %pith))
[head ship]
==

View File

@ -4,4 +4,10 @@
:- [%node %$ %hoon]
|= nod=node
^- hoon
(~(vol manx-utils nod) "text")
=* mu ~(. manx-utils nod)
=/ raw=tape (need (val:mu "text"))
%- crip
?: =(0 (lent raw)) raw
?. =((rear raw) '\0a') raw
:: remove newline added by html encoding nonsense
(snip raw)

View File

@ -0,0 +1,10 @@
/@ node
/@ message
/- manx-utils
:- [%node %$ %message]
|= nod=node
^- message
=/ text (~(vol manx-utils nod) "text")
=/ ship (slav %p (~(vol manx-utils nod) "ship"))
=/ date (slav %da (~(vol manx-utils nod) "date"))
[ship date text]

View File

@ -0,0 +1,45 @@
/@ node
/@ messenger-diff
/- manx-utils
:- [%node %$ %messenger-diff]
|= nod=node
^- messenger-diff
=/ mu ~(. manx-utils nod)
=/ head (@tas (got:mu %head))
~& >>> head
%- messenger-diff
?+ head
~| [%unknown-head head]
!!
%new-dm
=/ partner `@p`(slav %p (vol:mu "invites"))
[head partner]
::
%new-groupchat
=/ invites=tape (need (val:mu "invites"))
=/ parsed-invites=(set @p)
%- silt
%+ scan (weld " " invites)
%- star
;~ pose
;~ pfix (jest ' ~')
fed:ag
==
;~ pfix (jest ', ~')
fed:ag
==
==
~& > parsed-invites
=/ value (val:mu "name")
=/ name=cord
:: TODO: if invites are longer than some amount of character
?~ value (crip invites)
(crip (need value))
~& > name
[head name parsed-invites]
::
%invite-to-groupchat
~& > (vol:mu "name")
=/ name (vol:mu "name")
[head name ~]
==

View File

@ -1,41 +1,15 @@
/@ node
/@ sail
/- manx-utils
/- sail
:- [%node %$ %sail]
|= nod=node
|^
^- sail
=/ mu ~(. manx-utils nod)
=/ code
=/ raw=tape (need (val:mu "code"))
?: =(0 (lent raw)) (crip raw)
?. =((rear raw) '\0a') (crip raw)
(crip (snip raw))
=/ class (vol:mu "classes")
[code class `(render-udon code)]
++ render-udon
|= code=@t
^- (each manx tang)
=/ newline (trip 10)
=/ udon
:: format as udon document
%- crip
;: welp
";>" newline newline
(trip code) newline
==
=/ mul
%- mule
|.
!< manx
%+ slap
;: slop
!>(..zuse)
!>(manx-utils=manx-utils)
==
(ream udon)
?- -.mul
%.y [%.y (manx p.mul)]
%.n [%.n (tang p.mul)]
==
--
=/ mu ~(. manx-utils nod)
=/ code
=/ raw=tape (need (val:mu "code"))
%- crip
?: =(0 (lent raw)) raw
?. =((rear raw) '\0a') raw
:: remove newline by html encoding nonsense
(snip raw)
=/ class (vol:mu "classes")
[code class `(render-udon:sail code)]

View File

@ -0,0 +1,10 @@
/@ node
/@ send-poke
/- manx-utils
:- [%node %$ %send-poke]
|= nod=node
^- send-poke
=/ mu ~(. manx-utils nod)
:+ (slav %ud (got:mu %x))
(slav %ud (got:mu %y))
(pave:neo (rash (got:mu %pith) stap))

View File

@ -7,17 +7,8 @@
=* mu ~(. manx-utils nod)
=/ head (@tas (got:mu %head))
?+ head !!
%new-tab
[%new-tab ~]
%minimize
[%minimize (slav %ud (got:mu %hawk-slot))]
%slide-up
[%slide-up (slav %ud (got:mu %hawk-slot))]
%slide-down
[%slide-down (slav %ud (got:mu %hawk-slot))]
%maximize
[%maximize (slav %ud (got:mu %hawk-slot))]
%close
[%close (slav %ud (got:mu %hawk-slot))]
%menu
=/ c ?~((get:mu %closed) %.y %.n)
[%menu c]
::
==

View File

@ -4,9 +4,14 @@
:- [%node %$ %sky]
|= nod=node
^- sky
:_ (slav %ud (~(got manx-utils nod) %slots))
%+ murn c.nod
|= =manx
=/ here (rush (~(got manx-utils manx) %here) stap)
?~ here ~
`[*@da (pave:neo u.here)]
:*
%+ murn c.nod
|= =manx
=/ here (rush (~(got manx-utils manx) %here) stap)
?~ here ~
`[*@da (pave:neo u.here)]
::
(slav %ud (~(got manx-utils nod) %slots))
::
%.n
==

View File

@ -0,0 +1,24 @@
/@ node
/@ slideshow-diff
/- manx-utils
:- [%node %$ %slideshow-diff]
|= nod=node
^- slideshow-diff
=* mu ~(. manx-utils nod)
=/ head (got:mu %head)
?+ head !!
%mode
:- %mode
%+ snag 0
%+ murn c.nod
|= =manx
=/ ribs (malt a.g.manx)
=/ cls (fall (~(get by ribs) %class) "")
?~ x=(find "toggled" cls) ~
:- ~
%- ?(%edit %both %preview %present)
(crip (~(got by ribs) %mode))
::
%slide
[%slide (slav %ud (got:mu %current))]
==

View File

@ -0,0 +1,18 @@
/@ node
/@ slideshow
/- manx-utils
/- sh=slideshow
:- [%node %$ %slideshow]
|= nod=node
|^
^- slideshow
=/ mu ~(. manx-utils nod)
=/ code
=/ raw=tape (need (val:mu "code"))
%- crip
?: =((lent raw) 0) raw
?. =((rear raw) '\0a') raw
(snip raw)
=/ class (vol:mu "classes")
[code class `(render-slideshow:sh code) %both 0]
--

View File

@ -1,49 +1,64 @@
/@ node
/@ node :: manx
::
:: $task-diff
:: $% [%new =task prepend=?]
:: [%edit text=cord done=?]
:: [%oust =pith]
:: [%reorder order=(list pith)]
:: ==
/@ task-diff
:: import lib/manx-utils
/- manx-utils
::
:: declare that this is a conversion from a
:: dynamic XML node to task-diff
:- [%node %$ %task-diff]
|= nod=node
^- task-diff
=/ mu ~(. manx-utils nod)
:: extract head attribute from XML node
=/ head (@tas (got:mu %head))
%- task-diff
?+ head
~| [%unknown-head head]
!!
%become
=/ path (stab (vol:mu "path"))
[head (pave:neo path)]
::
%nest
=/ name (vol:mu "name")
[head name '' | ~]
::
%prep
=/ name (vol:mu "name")
[head name '' | ~]
::
%oust
=/ path (stab (got:mu %pith))
[head (pave:neo path)]
%new
:: extract text and prepend attributes from XML node
=/ text (vol:mu "text")
=/ prepend (vol:mu "prepend")
?: =(prepend 'prepend')
:: construct the task-diff
[head [text | & ~] &]
[head [text | & ~] |]
::
%edit
=/ text (vol:mu "text")
:: extract text attribute from XML node
=/ text (vol:mu "text")
:: extract checked attribute from done element in XML node
=/ done-el (need (named:mu "done"))
=/ done (~(has by (malt a.g.done-el)) %checked)
=/ done (~(has by (malt a.g.done-el)) %checked)
:: construct the task-diff
[head text done]
::
%kid-done
%oust
:: extract pith attribute from XML node
=/ path (stab (got:mu %pith))
:: construct the task-diff
[head (pave:neo path)]
::
%reorder
=/ piths
::
:: extracting here attribute from each node in XML
:: list and return as last element of here path
%+ turn c.nod
|= =manx
=/ here (get:mu %here)
=/ mu-reorder ~(. manx-utils manx)
=/ here (get:mu-reorder %here)
?~ here
~& >>> [%bad-here manx]
!!
(pave:neo /[(rear (stab (crip (need here))))])
:: construct the task-diff
[head piths]
==

View File

@ -0,0 +1,21 @@
/@ todos
/@ node
/- manx-utils
/- html-utils
:- [%node %$ %todos]
|= nod=node
^- todos
|^
=* mu ~(. manx-utils nod)
=* hu ~(. mx.html-utils nod)
:- (is-checked (need (named:mu "show-done")))
%+ turn c.q:(need (gen:hu "todos"))
|= =manx
^- [done=? name=@t]
=. nod manx
:- (is-checked (need (named:mu "done")))
(vol:mu "todo-name")
++ is-checked
|= =manx
(~(has by (malt a.g.manx)) %checked)
--

View File

@ -4,5 +4,4 @@
:- [%node %$ %txt]
|= nod=node
^- txt
(~(vol manx-utils nod) "text")
(~(vol manx-utils nod) "text")

View File

@ -0,0 +1,62 @@
/@ number :: @ud
:: import /lib/feather-icons
/- feather-icons
:: declare that this is a conversion from number to HTMX
:- [%number %$ %htmx]
::
:: this gate accepts a number and a bowl:neo;
:: we'll access bowl:neo in the UI to access the
:: here.bowl of the shrub that's using this /con file
|= =number
|= =bowl:neo
::
:: this gate returns a manx, which is what Hoon uses
:: to store dynamic XML nodes; in this case we'll use
:: Sail to specify a manx that expects the HTMX library
:: to be available on the frontend
^- manx
::
:: open a <div class="p3 fc g2 ac br2">
:: these utility classes are specified in feather.css,
:: which this /con file expects on the frontend
;div.p3.fc.g2.ac.br2
:: <h1>Counter</h1>
;h1: Counter
:: <p>{number}</p>
;p: {<number>}
:: open a <form> with HTMX attributes
;form
::
:: hx-post will issue a POST request to the provided
:: url and swap response into the DOM
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=counter-diff"
::
:: hx-target specifies the target for hx-post's DOM
:: swap: the element with class "loading"
=hx-target "find .loading"
::
:: hx-swap specifies how the response to hx-post's
:: request will be swapped in relative to the target
=hx-swap "outerHTML"
::
:: here, the head attribute specifies the poke that
:: hx-post will send to the target shrub; look at
:: /con/node-counter-diff.hoon for more on =head
=head "inc"
::
:: below, the classes "loaded", "loader", and
:: "loading" provide loading spinner behavior on
:: sending and receiving this form's POST request
::
:: <button class="bd1 br1 pr b1 hover loader">
;button.bd1.br1.p2.b1.hover.loader
:: <span class="loaded">Increment</span>
;span.loaded: Increment
:: <span class="loading">
;span.loading
:: import +loading sail from /lib/feather-icons
;+ loading.feather-icons
== :: </span>
== :: </button>
== :: </form>
== :: </div>

View File

@ -120,7 +120,7 @@
|= [=pith =idea:neo]
^- (unit pent)
?~ pith ~
=/ entry !<(planner-entry q.pail.idea)
=/ entry !<(planner-entry q.q.saga.idea)
?. ?&
(gte when.entry d)
(lth when.entry (add d ~d1))
@ -141,7 +141,7 @@
%+ murn ~(tap of:neo kids.bowl)
|= [=pith =idea:neo]
^- (unit pent)
=/ entry !<(planner-entry q.pail.idea)
=/ entry !<(planner-entry q.q.saga.idea)
?. ?&
(gte d when.entry)
(lte d (add when.entry length.entry))

View File

@ -10,7 +10,6 @@
=id "tabs-{id}"
;+ editor
;+ viewer
;+ docs
==
==
++ id
@ -58,15 +57,6 @@
;
; view
==
;label.fr.je.g1.grow.hidden
;input
=name "view"
=type "radio"
=oninput "$('#tabs-{id}').children().addClass('hidden');$('#docs-{id}').removeClass('hidden');"
;
==
;span: docs
==
==
++ editor
;form.fc.p1.g1.hidden.grow.basis-half.scroll-y.relative
@ -141,716 +131,4 @@
+.res
==
==
:: XX import from source file instead of copying
++ docs
;div.grow.border.basis-half.scroll-x.scroll-y.hidden
=id "docs-{id}"
=style "min-width: 300px; height: 100%;"
;div.prose.p3
# Intro to Feather
Feather is a _Design System_ which gives
Sail developers easy access to a small set of
predefined styles.
Feather is implemented as a library of CSS classes,
and is bundled with Sky and Hawk.
Benefits of opting into
Feather's constraints:
- Feather uses CSS variables that...
- the user can override
- respect OS light/dark theme
- UIs styled with Feather fit in
with the style of Sky and Hawk
- "birds of a feather flock together"
-----------------------------------------------
## The `prose` Class
the `.prose` class adds blog-like spacing,
sizing, and readability styling to semantic
children such as `p`
`li` `h1` `h2` `a` etc.
This document uses it.
```
;article.prose
# cool
- neat
- fine
==
```
-----------------------------------------------
## Flexbox
;*
%+ turn
:~
"fr jb"
"fr ja"
"fr jc"
"fr js"
"fr je"
"fc ac"
"fc as"
"fc ae"
"fc af"
==
|= =tape
;div.fc.g1.br1(style "margin-top: 20px;")
;code: {tape}
;div
=class "bd1 br1 p3 g1 {tape}"
;* %+ turn (gulf 1 3)
|= n=@
;b.b1.f3.p1.br1: X
==
==
;div.fc.g3(style "margin-top: 50px;")
;*
%+ turn
:~
"fr g0"
"fr g1"
"fr g2"
"fr g3"
"fr g4"
"fr g5"
"fr g6"
"fr g7"
"fr g8"
==
|= =tape
;div
=class "s-2 mono {tape}"
;div.bd1.p2: {tape}
;* %+ turn (gulf 1 3)
|= n=@
;div.bd1.p2;
==
==
;div.fr.g2(style "margin-top: 50px;")
;*
%+ turn
:~
"fc g0"
"fc g1"
"fc g2"
"fc g3"
"fc g4"
"fc g5"
"fc g6"
"fc g7"
"fc g8"
==
|= =tape
;div
=class "s-2 mono {tape}"
;div.bd1.p1(style "width: min-content;"): {tape}
;* %+ turn (gulf 1 3)
|= n=@
;div.bd1.p2;
==
==
;div.frw.g2(style "margin-top: 50px;")
;*
%+ turn (gulf 1 10)
|= n=@
;div.p2.bd1.mono: frw g2
==
;div.f2.mono
;div.fr.g4.p2.bd1.br1(style "margin-top: 50px;")
;div.p2.br1.b1;
;div.p2.br1.b1.tc.grow: grow
;div.p2.br1.b1;
==
;div.fr.g4.p2.bd1.br1(style "margin-top: 20px;")
;div.p2.br1.b1;
;div.p2.br1.b1;
;div.p2.br1.b1.tc.grow: grow
==
;div.fr.g4.p2.bd1.br1(style "margin-top: 20px;")
;div.p2.br1.b1.tc.grow: grow
;div.p2.br1.b1;
;div.p2.br1.b1.tc.grow: grow
==
==
-----------------------------------------------
## Typography
;div.frw.g1.br1.ac.jc
;*
%+ turn
:~
"s-2"
"s-1"
"s0"
"s2"
"s3"
"s4"
"s5"
"s6"
==
|= =tape
;span
=class "p2 {tape}"
; {tape}
==
==
;div.frw.g1.br1.ac.jc
;*
%+ turn
:~
"mono"
"bold"
"italic"
"underline"
"strike"
==
|= =tape
;span
=class "p2 {tape}"
; {tape}
==
==
-----------------------------------------------
## Text Alignment
;*
%+ turn
:~
"tl"
"tc"
"tr"
==
|= =tape
;div
=class "mono {tape}"
; {tape}
==
-----------------------------------------------
## Foreground & Background Colors
;div.frw.g1.br1.ac.jc.mono
;*
%+ turn
:~
"f-3"
"f-2"
"f-1"
"f0"
"f1"
"f2"
"f3"
"f4"
==
|= =tape
;span
=class "p2 bold {tape}"
; {tape}
==
==
;div.frw.g1.br1.ac.jc.mono
;*
%+ turn
:~
"b-3"
"b-2"
"b-1"
"b0"
"b1"
"b2"
"b3"
"b4"
==
|= =tape
;span
=class "p2 {tape}"
; {tape}
==
==
-----------------------------------------------
## Padding
;div.fc.ac.g2
;*
%+ turn
:~
"p-8"
"p-7"
"p-6"
"p-5"
"p-4"
"p-3"
"p-2"
"p-1"
"p0"
"p1"
"p2"
"p3"
"p4"
"p5"
"p6"
"p7"
"p8"
"p-page"
==
|= =tape
;div
=class "wfc mono f2 b1 {tape}"
; {tape}
==
==
-----------------------------------------------
## Margin
;div
;*
%+ turn
:~
"m0"
"ma"
"mt1"
"mt2"
"mt3"
==
|= =tape
;div
=class "mono f2 b2 br1 wfc p2 {tape}"
; {tape}
==
==
-----------------------------------------------
## Opacity
;div.frw.g2.ac.jc
;*
%+ turn
:~
"o0"
"o1"
"o2"
"o3"
"o4"
"o5"
"o6"
"o7"
"o8"
"o9"
"o10"
==
|= =tape
;div.fc.g1.ac.jc
;span.mono.s-1.f2: {tape}
;div
=class "wfc mono f0 b1 p4 bd1 {tape}"
;
==
==
==
-----------------------------------------------
## Borders
;div.frw.ac.g2(style "margin-top: 20px;")
;*
%+ turn
:~
"bd0"
"bd1"
"bd2"
"bd3"
==
|= =tape
;div
=class "wfc mono f2 p2 {tape}"
; {tape}
==
==
;div.frw.ac.g2(style "margin-top: 20px;")
;*
%+ turn
:~
"br0"
"br1"
"br2"
"br3"
==
|= =tape
;div
=class "wfc mono f2 p2 bd1 {tape}"
; {tape}
==
==
-----------------------------------------------
## Dimensions
- `wf` width: full
- `wfc` width: fit-content
- `mw-page` max-width: page (650px)
- `hf` height: full
- `hfc` height: fit-content
-----------------------------------------------
## Special
;*
%+ turn
:~
"toggled"
"hover"
==
|= =tape
;button(style "margin: 5px;")
=class "wfc mono f0 b0 p2 bd1 {tape}"
; {tape}
==
the `hidden` class hides the element.
-----------------------------------------------
## Request indicators
`loader` indicator parent
`loading` loading state
`loaded` loaded state
;details.mt1
;summary: Example
;button.mt1.b1.p1.hover.br1.bd1.f1.loader
=onclick "$(this).toggleClass('htmx-request')"
;span.loaded: toggle load state
;span.loading.f1: ...loading
==
```
;button.loader
=onclick "$(this).toggleClass('htmx-request')"
;span.loaded: toggle load state
;span.loading: ...loading
==
```
==
-----------------------------------------------
## Misc
`scroll-y`\
scroll-y: auto
`scroll-x`\
scroll-x: auto
`relative`\
position: relative
`sticky`\
position: sticky
`absolute`\
position: absolute
`fixed`\
postion: fixed
`block`\
display: block
`inline`\
display: inline-block
`pre`\
white-space: pre
`pre-line`\
white-space: pre-line
`break`\
word-break: break-word
`action`\
touch-action: manipulation
-----------------------------------------------
## Feather Sutra
> Order requires constraints.
### Primary Constraint
Interfaces must work on a mobile web browser.
### Consequent Constraints
- no element should need to be wider than 350px
- avoid horizontal scrolling
- left-click is the only mouse event
### Locality of Behavior
- co-locate actionable elements with the elements on which they act
- put styling inline
- put javascript inline
### Mechanical Simplicity
- don't fight the browser
- avoid animations
- prefer [bubbling-event](https://htmx.org/attributes/hx-on/)
architectures
- store state as text attributes in the DOM, not javascript state
### UX low-hanging fruit
- network requests should always have indicators
- prefer autosave to manually saving
- be semantic with your tags
-----------------------------------------------
## Feather Tantra
> Life evolves through a vital chaos.
```
;div.any.class(style "any: css;");
;div
=class "any class"
=style
"""
any: "css";
that-you: "want;
"""
;
==
;style
;+ ;/ %- trip
'''
.any-css {
that-you: "want";
}
'''
==
;script
;+ ;/ %- trip
'''
// any javascript you want
'''
==
```
;div.end;
;style
;+ ;/ %- trip
'''
details {
display: flex !important;
flex-direction: column;
padding: 12px;
background: var(--b1);
box-sizing: border-box;
}
.end {
margin-bottom: 450px;
}
hr {
margin: 100px 0;
}
'''
==
==
==
--

View File

@ -1,26 +1,65 @@
/@ sky
/@ sky-settings
/- feather-icons
/* date-now
/* a-i-r
/* feather
/* reset
/* hawk-icon
/* jquery
/* htmx-js
/* htmx-response-targets
/* htmx-idiomorph
:- [%sky %$ %htmx]
|= =sky
|= =bowl:neo
^- manx
|^
;div.wf.hf.relative
;a-i-r.wf.hf.relative
=style "opacity: var(--sky-opacity); padding: var(--sky-outer-gap);"
=id "air"
=hawks "{<open.sky>}"
=morph-retain "closed"
;+ menu-btn
;+ menu-btn-style
;+ theme-style
;+ nav
;* p:(spin (scag open.sky hawks.sky) 0 ha-wk)
==
;+ eye
=; m
%- lift
?: menu.sky m
m(a.g [[%closed ""] a.g.m])
^- manx
;a-i-r.wf.hf.relative
=style "opacity: var(--sky-opacity); padding: var(--sky-outer-gap);"
=id "air"
=hawks "{<open.sky>}"
=morph-retain "closed"
=hx-on-hawks-moved hawks-moved-js
=hx-on-htmx-after-request
"""
let verb = event.detail.requestConfig.verb;
let url = new URL(event.detail.xhr.responseURL);
let pams = new URLSearchParams(url.search);
if (verb === 'get' &&
url.pathname.startsWith('/neo/hawk/~') &&
!pams.has('no-save')
) \{
$('.nav-refresher').emit('refresh');
}
"""
;* p:(spin (scag open.sky hawks.sky) 0 ha-wk)
;+ menu-btn
;+ nav
==
::
++ hawks-moved-js
:: js to run whenever the order or number of hawks changed.
:: it will:
:: - trigger a refresh of the nav
:: - loop through any slotted hawks and reslot them starting from 0
%- trip
'''
let air = $(this);
let num = parseInt(air.attr('hawks'));
let hawks = air.children('[slot]').filter('.hawk').get();
hawks.sort((a, b) => {
return (a.getAttribute('slot') < b.getAttribute('slot')) ? -1 : 1;
}).forEach((h, i) => {
h.setAttribute('slot', `s${i}`);
});
air.find('.nav-refresher').emit('refresh');
'''
++ map-to-css-tape
|= m=(map @t @t)
^- tape
@ -37,7 +76,7 @@
?~ s ~
:- ~
!< sky-settings
q.pail.u.s
q.q.saga.u.s
;style
;+ ;/
?~ settings
@ -49,9 +88,17 @@
"""
==
++ menu-btn
=; m
?: menu.sky m
m(a.g [[%closed ""] a.g.m])
^- manx
;button.hover.f2.b2.fc.ac.jc.air-btn.wf
=slot "button"
=onclick "$(this).closest('a-i-r').attr('closed', !$(this).parent().attr('closed'))"
=onclick "this.parentNode.toggleAttribute('closed'); this.toggleAttribute('closed');"
=hx-post "/neo/hawk/{<our.bowl>}/sky?stud=sky-diff"
=head "menu"
=hx-target "this"
=hx-swap "none"
;div.fc.ac.jc.bold.s3.f3(style "height: 2rem;"): ~
==
++ menu-btn-style
@ -67,8 +114,8 @@
@media(max-width: 900px) {
.air-btn {
position: absolute;
bottom: 25px;
right: 25px;
bottom: 45px;
left: 25px;
padding: 30px;
width: 70px;
height: 70px;
@ -89,26 +136,30 @@
?: =(pith /) ""
(en-tape:pith:neo pith)
=/ idt `tape`(zing (scan +:(scow %da id) (most dot (star ;~(less dot prn)))))
;div.wf.hf.br1
;div.wf.hf.fc.jc.ac.f2.s3.spinner
=slot "s{<a>}"
=id "hawk-windshield-{idt}"
;div.wf.hf.fc.jc.ac.f2.s3.spinner
=id "hawk-{idt}"
=morph-if-class "spinner"
;+ loading.feather-icons
==
;div.hidden
=hx-get "/neo/hawk{ext}?slot={<a>}&id={<id>}&no-save"
=hx-trigger "load"
=hx-target "#hawk-{idt}"
=hx-swap "morph"
;
==
=id "hawk-{idt}"
=hx-get "/neo/hawk{ext}?slot={<a>}&hawk-id={<id>}&no-save"
=morph-retain "slot"
=hx-trigger "load"
=hx-target "this"
=hx-swap "morph"
;+ loading.feather-icons
==
++ nav
;nav.wf.hf.p2.fc.g2
;nav.wf.hf.p2.fc.g2.sky-nav
=slot "nav"
;div.mt2.o0;
;div.nav-refresher.loader.p3
=hx-get "/neo/sky"
=hx-swap "outerHTML"
=hx-target "closest nav"
=hx-select "nav.sky-nav"
=hx-trigger "refresh"
;span.loaded;
;span.loading
;+ loading.feather-icons
==
==
;+ new-tab
;*
=< p
@ -119,16 +170,46 @@
:_ +(a)
=/ idt `tape`(zing (scan +:(scow %da id) (most dot (star ;~(less dot prn)))))
=/ color (trip ?:((lth a open.sky) 'b2' 'b1'))
=/ close-hawk-js
%- trip
'''
let toClose = parseInt(this.getAttribute('close'));
let air = $('a-i-r');
let num = parseInt(air.attr('hawks'));
let hawks = air.children('[slot]').filter('.hawk');
if (toClose < num) {
air.attr('hawks', hawks.length-1);
}
hawks.each(function() {
let slot = parseInt($(this).attr('slot').slice(1));
if (slot == toClose) {
$(this).remove();
}
});
$(this).emit('hawks-moved');
'''
:: =/ maximize-hawk-js
:: %- trip
:: '''
:: let air = $('a-i-r');
:: let num = parseInt(air.attr('hawks'));
:: air.attr('hawks', num+1);
:: let toMax = '#hawk-' + this.getAttribute('hawk');
:: $(toMax).attr('slot', 's-1');
:: $(this).emit('hawks-moved');
:: '''
;div
=id "hawk-tab-{idt}"
=hx-ext "ignore:html-enc"
=class "fr ac br1 {color}"
;button
=class "loader p2 tl br1 hover grow {color}"
=hx-post "/neo/hawk/{<our.bowl>}/sky?stud=sky-diff"
=hx-post "/neo/hawk/{<our.bowl>}/sky?stud=sky-diff&head=maximize&slot={<a>}"
:: XX optimistically render
::=hx-on-htmx-after-request maximize-hawk-js
=hawk idt
=hx-target "find .loading"
=hx-swap "outerHTML"
=head "maximize"
=hawk-slot "{<a>}"
;span.loaded: {(en-tape:pith:neo pith)}
;span.loading
;+ loading.feather-icons
@ -136,11 +217,10 @@
==
;button
=class "loader p2 tl br1 hover {color}"
=hx-post "/neo/hawk/{<our.bowl>}/sky?stud=sky-diff"
=hx-target "find .loading"
=hx-swap "outerHTML"
=head "close"
=hawk-slot "{<a>}"
=close "{<a>}"
=hx-post "/neo/hawk/{<our.bowl>}/sky?stud=sky-diff&head=close&slot={<a>}"
=hx-swap "none"
=hx-on-htmx-after-request close-hawk-js
;span.loaded.f3
;+ close.feather-icons
==
@ -151,15 +231,18 @@
==
==
++ new-tab
:: XX optimistically render
;button.loader.b2.p2.tc.br1.hover.wfc.s-1
=hx-post "/neo/hawk/{<our.bowl>}/sky?stud=sky-diff"
=hx-ext "ignore:html-enc"
=hx-post "/neo/hawk/{<our.bowl>}/sky?stud=sky-diff&head=new-hawk"
=hx-target "find .loading"
=hx-swap "outerHTML"
=hx-on-htmx-after-request "$(this).emit('hawks-moved')"
=type "button"
=head "new-tab"
=hx-vals (trip 'js:{now: urbitTimestamp()}')
;span.loaded.fr.ac.js.g2
;+ add.feather-icons
;span.f3: new tab
;span.f3: new window
==
;span.loading
;+ loading:feather-icons
@ -175,15 +258,18 @@
function handleKey(e) {
let focused = document.activeElement;
let textarea = ['TEXTAREA'].includes(focused.nodeName)
let textinput = ['text', 'number', 'email', 'password'].includes(focused.getAttribute('type'));
let textinput = ['text', 'number', 'email', 'password'].includes(focused.type);
if (textarea || textinput) {
if (e.key === 'Escape') {
closeEye();
document.activeElement.blur();
}
return;
}
if (e.key === ' ') {
else if (e.key === 'Escape') {
closeEye();
document.activeElement.blur();
}
else if (e.key === ' ') {
e.preventDefault();
if (window.eye.open) {
closeEye();
@ -256,4 +342,204 @@
'''
==
==
++ icon-url
^~
%- trip
%^ cat
3
'data:image/png;base64,'
%- ~(en base64:mimes:html & |)
(as-octs:mimes:html hawk-icon)
++ favicon
^~
=; m m(a.g [[%href icon-url] a.g.m])
^- manx
;link
=rel "icon"
=type "image/png"
;
==
++ manifest-url
^~
%- trip
%^ cat
3
'data:application/json;utf-8,'
%- en:json:html
%- pairs:enjs:format
:~
['name' s+'sky']
['description' s+'an urbit namespace viewer']
['start_url' s+'http://localhost/neo/sky'] :: XX
['display' s+'standalone']
['background_color' s+'black']
:+ 'icons' %a
:~
%- pairs:enjs:format
:~
['src' s+(crip icon-url)]
['sizes' s+'196x196']
['type' s+'image/png']
==
==
==
++ manifest
^~
=; m m(a.g [[%href manifest-url] a.g.m])
^- manx
;link
=rel "manifest"
;
==
++ htmx-extensions
:: htmx extension which encodes the request
:: as the serialized HTML of the calling element
::
:: XX usage of this should be optional.
:: requests should default to form-encoded.
%- trip
'''
htmx.defineExtension('html-enc', {
onEvent: function (name, evt) {
if (name === "htmx:configRequest") {
evt.detail.headers['Content-Type'] = "text/html";
}
},
encodeParameters : function(xhr, parameters, elt) {
xhr.overrideMimeType('text/html');
let xmls = new XMLSerializer();
return (xmls.serializeToString(elt));
}
});
Idiomorph.defaults.ignoreActive = true;
Idiomorph.defaults.callbacks.beforeAttributeUpdated = (name, node, type) => {
if (node.hasAttribute('morph-retain')) {
let ribs = node.getAttribute('morph-retain').split(',').map(t => t.trim());
if (ribs.includes(name)) {
return false;
}
}
}
Idiomorph.defaults.callbacks.beforeNodeMorphed = (oldNode, newNode) => {
if (oldNode?.nodeName !== "#text") {
if (oldNode.hasAttribute('morph-no-swap') && oldNode.id === newNode.id) {
return false;
}
else if (
newNode.hasAttribute('morph-if-class') &&
!oldNode.classList.contains(newNode.getAttribute('morph-if-class'))
) {
return false;
}
}
}
'''
::
++ lift
|= in=manx
^- manx
;html
;head
;meta(charset "UTF-8");
;title: s k y
;script: {(trip jquery)}
;script: {(trip htmx-js)}
;script: {(trip htmx-response-targets)}
;script: {(trip htmx-idiomorph)}
;script: {htmx-extensions}
;link
=rel "stylesheet"
=href "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.15.1/cdn/themes/light.css"
;
==
;script
=type "module"
=src "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.15.1/cdn/shoelace.js"
;
==
;meta
=name "viewport"
=content
"""
width=device-width,
initial-scale=1.0,
maximum-scale=1.0
"""
;
==
;meta
=name "htmx-config"
=content (trip '{"ignoreTitle":"true"}')
;
==
::;style
:: ;+ ;/ %- trip
:: '''
:: @font-face {
:: font-family: 'Urbit Sans';
:: src: url("https://media.urbit.org/fonts/UrbitSans/UrbitSansVFWeb-Regular.woff2") format("woff2");
:: font-style: normal;
:: font-weight: 100 700;
:: }
:: '''
::==
;style: {(trip reset)}
;style: {(trip feather)}
;script
;+ ;/ %- trip
'''
window.log = function() {
if (this.console) {
console.log(Array.prototype.slice.call(arguments));
}
};
jQuery.fn.log = function (msg) {
console.log(msg, this);
return this;
};
jQuery.fn.emit = function (name) {
(this[0]).dispatchEvent(
new Event(
name,
{ bubbles: true, cancelable: true, composed: true }
)
);
return this;
};
function urbitTimestamp() {
let now = new Date();
let year = now.getFullYear();
let month = now.getMonth() + 1;
let date = now.getDate();
let hour = String(now.getHours()).padStart(2, '0');
let min = String(now.getMinutes()).padStart(2, '0');
let sec = String(now.getSeconds()).padStart(2, '0');
return `~${year}.${month}.${date}..${hour}.${min}.${sec}`;
}
'''
==
;script: {(trip a-i-r)}
;script: {(trip date-now)}
;+ favicon
;+ manifest
==
;body
=hx-ext "html-enc,response-targets,morph"
=hx-swap "outerHTML"
=hx-boost "true"
=hx-history "false"
=hx-replace-url "/neo/sky"
=style
"""
background-color: var(--b1);
background-image: var(--sky-bg-url);
background-size: var(--sky-bg-size);
background-repeat: var(--sky-bg-repeat);
"""
;+ in
;+ eye
;+ menu-btn-style
;+ theme-style
==
==
--

View File

@ -39,7 +39,7 @@
==
++ home-button
;a.p2.br1.bd1.b1.hover.loader.wfc.block
=href "/neo/hawk/home"
=href "/neo/hawk/{(scow %p our.bowl)}/home"
=hx-swap "innerHTML"
=hx-target "closest .hawk"
=hx-select ".hawk"

View File

@ -0,0 +1,293 @@
/@ slideshow
/- feather-icons
:- [%slideshow %$ %htmx]
|= =slideshow
|= =bowl:neo
|^
;div.slideshow.fc.relative.wf.hf
;+ controls
;div.frw.js.af.scroll-y.hf
;+ editor
;+ previewer
;+ deck
==
;+ script
;+ style
==
++ controls
=/ cls "p-1 br1 b1 hover"
;form.p2.frw.js.ac.g3.sticky.wf
=style "top:0; left: 0;"
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=slideshow-diff"
=hx-swap "none"
=head "mode"
;button
=class "{cls} {(trip ?:(=(%edit mode.slideshow) 'toggled' ''))}"
=morph-retain "class"
=type "submit"
=onclick "shChangeMode(this, ['.sh-editor'])"
=mode "edit"
; edit
==
;button
=class "{cls} {(trip ?:(=(%both mode.slideshow) 'toggled' ''))}"
=morph-retain "class"
=type "submit"
=onclick "shChangeMode(this, ['.sh-editor', '.sh-previewer'])"
=mode "both"
; both
==
;button
=class "{cls} {(trip ?:(=(%preview mode.slideshow) 'toggled' ''))}"
=morph-retain "class"
=type "submit"
=onclick "shChangeMode(this, ['.sh-previewer'])"
=mode "preview"
; preview
==
;div.grow;
;button
=class "{cls} {(trip ?:(=(%present mode.slideshow) 'toggled' ''))}"
=morph-retain "class"
=type "submit"
=onclick "shChangeMode(this, ['.deck'])"
=mode "present"
; present
==
==
++ editor
=/ open ?|(=(%edit mode.slideshow) =(%both mode.slideshow))
=/ hid ?:(open "" "hidden")
;div
=class "sh-editor sh-tab {hid} basis-half grow fc"
=morph-retain "class"
;form
=class "fc p1 g1 scroll-y relative grow"
=style "min-width: 300px; height: 100%;"
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=slideshow"
=hx-swap "morph"
=hx-target "closest .slideshow"
=hx-select ".slideshow"
=hx-trigger "input changed delay:0.4s from:find textarea, input changed delay:0.4s from:find input"
;input.p2.mono.bd1.br1
=name "classes"
=placeholder "prose p3"
=type "text"
=autocomplete "off"
=value (trip class.slideshow)
=oninput "$(this).attr('value', this.value);"
;
==
;textarea.p-page.pre.mono.scroll-x.grow.bd1.m0.br1
=name "code"
=morph-no-swap ""
=oninput "this.setAttribute('value', this.value);"
=spellcheck "false"
=value (trip code.slideshow)
=placeholder "# new slideshow"
; {(trip code.slideshow)}
==
;div.absolute
=style "top: 11px; right: 11px;"
;div.loader
;span.loaded(style "opacity: 0;"): ---
;span.loading
;+ loading.feather-icons
==
==
==
==
==
++ error
|= =tang
;div.fc.g3.p3.s0
;div.pre.mono
;*
%+ turn (scag 25 tang)
|= =tank
;span: {(of-wall:format (~(win re tank) 0 80))}
==
;div.pre.numbered.mono
;span: ;>
;span;
;*
%+ turn (to-wain:format code.slideshow)
|= t=@t
;span: {(trip t)}
==
==
++ previewer
=/ open ?|(=(%preview mode.slideshow) =(%both mode.slideshow))
=/ hid ?:(open "" "hidden")
;main
=class "sh-previewer sh-tab grow hf basis-half scroll-x scroll-y br1 {hid}"
=style "min-width: 300px;"
=morph-retain "class"
;+
?~ result.slideshow
;div.prose.p3
;h1: empty slideshow
==
=/ res u.result.slideshow
?- -.res
%.n (error +.res)
%.y
;div.p-page
;*
=< p
%^ spin
`(list manx)`+.res
0
|= [m=manx a=@ud]
:_ +(a)
=/ bdr ?:(=(slide.slideshow a) "bd2" "bd1")
;div.mw-page.ma.mt3.p1.bd1
;p.f3: {<+(a)>}
;div
=style "container-type: inline-size;"
;+ m(a.g [[%class (trip class.slideshow)] a.g.m])
==
==
==
==
==
++ deck
=/ hid ?:(=(%present mode.slideshow) "" "hidden")
;form
=class "deck b0 sh-tab grow basis-half scroll-x scroll-y wf hf {hid}"
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=slideshow-diff"
=hx-trigger "sh-change-slide"
=hx-swap "none"
=head "slide"
=current (scow %ud slide.slideshow)
=tabindex "0"
=onkeydown "shHandleKey(event, $(this))"
=style "min-width: 300px; height: 100%;"
=morph-retain "class"
;+
?~ result.slideshow
;div.prose.p3
;h1: empty slideshow
==
=/ res u.result.slideshow
?- -.res
%.n (error +.res)
%.y
=/ slides `(list manx)`+.res
=/ prev-t (scow %ud ?~(slide.slideshow 0 (dec slide.slideshow)))
=/ next-t (scow %ud (min (dec (lent slides)) +(slide.slideshow)))
;div.relative.wf.hf
;div.present-controls.fr.g1.ac.js.absolute.p2.o7.z1
=style "top: 0; right: 0;"
;button.p2.b1.br1.bd1.hover
=type "button"
=onclick "shPrevious($(this).closest('.deck'))"
;+ chevron-left.feather-icons
==
;span.sh-counter.f3.p2.b1.br1: {<+(slide.slideshow)>}/{<(lent slides)>}
;button.p2.b1.br1.bd1.hover
=type "button"
=onclick "shNext($(this).closest('.deck'))"
;+ chevron-right.feather-icons
==
;button.sh-fs-tog.p2.b1.br1.bd1.hover
=type "button"
=onclick "shToggleFullscreen($(this).closest('.deck'));"
; ⛶
==
==
;*
=< p
%^ spin
slides
0
|= [m=manx a=@ud]
:_ +(a)
=/ hid ?:(=(slide.slideshow a) "" "hidden")
=/ extra "wf hf"
=/ cls "{(trip class.slideshow)} {extra}"
;div
=class "wf hf {hid}"
=sh-slide (scow %ud a)
;div.wf.hf
=style "container-type: inline-size;"
;+ m(a.g [[%class cls] a.g.m])
==
==
==
==
==
++ script
;script
;+ ;/ %- trip
'''
function shPrevious(deck) {
let current = deck.attr('current');
let i = parseInt(current);
let slides = deck.find('[sh-slide]');
let n = Math.max(0, i-1)
slides.addClass('hidden');
slides.filter(`[sh-slide='${n}']`).removeClass('hidden');
deck.attr('current', n);
deck.find('.sh-counter').text((n + 1)+`/${slides.length}`);
triggerSlideSave(deck[0]);
}
function shNext(deck) {
let current = deck.attr('current');
let i = parseInt(current);
let slides = deck.find('[sh-slide]');
let n = Math.min(slides.length-1, i+1)
slides.addClass('hidden');
slides.filter(`[sh-slide='${n}']`).removeClass('hidden');
deck.attr('current', n);
deck.find('.sh-counter').text((n + 1)+`/${slides.length}`);
triggerSlideSave(deck[0]);
}
function triggerSlideSave(that) {
const evt = new Event("sh-change-slide");
that.dispatchEvent(evt);
}
function shToggleFullscreen(deck) {
if (!!deck.attr('full')) {
deck.removeAttr('full');
document.exitFullscreen();
} else {
deck.attr('full', 'true');
deck[0]?.requestFullscreen();
}
}
function shHandleKey(ev, deck) {
let key = ev.key;
if (['ArrowLeft'].includes(key)) {
ev.preventDefault();
ev.stopPropagation();
shPrevious(deck);
} else if (['ArrowRight', ' '].includes(key)) {
ev.preventDefault();
ev.stopPropagation();
shNext(deck);
}
}
function shChangeMode(that, tabs) {
$(that).siblings().removeClass('toggled');
$(that).addClass('toggled');
$(that).closest('.slideshow').find('.sh-tab').addClass('hidden');
tabs.forEach(t => {
$(that).closest('.slideshow').find(t).removeClass('hidden');
$(that).closest('.slideshow').find(t)[0]?.focus();
})
}
'''
==
++ style
;style
;+ ;/ %- trip
'''
.slide {
overflow-y: auto;
padding: 4cqw 6cqw;
font-size: 2.4cqw;
}
'''
==
--

View File

@ -4,11 +4,4 @@
|= =bowl:neo
=/ =wall (zing (turn tan |=(t=tank (~(win re t) [0 80]))))
=/ =tape (zing (join "\0a" wall))
;textarea#input.wf.p2.pre.mono.grow
=name "text"
=placeholder "code"
=spellcheck "false"
=value tape
=oninput "this.setAttribute('value', this.value);"
; {tape}
==
;/(tape)

View File

@ -1,12 +1,16 @@
/@ task-diff
/- feather-icons
:- [%task-diff %$ %htmx]
|= t=task-diff
|= =bowl:neo
^- manx
;div.loading
=hx-get "/neo/hawk{(en-tape:pith:neo here.bowl)}"
=hx-target "closest .hawk"
=hx-get "/neo/hawk{(en-tape:pith:neo here.bowl)}"
=hx-target "closest .hawk"
=hx-indicator "closest .loader"
=hx-swap "innerHTML"
=hx-trigger "load"
; +++
=hx-swap "innerHTML"
=hx-trigger "load"
;span.loading
;+ loading.feather-icons
==
==

View File

@ -1,276 +1,364 @@
/@ task
/@ task :: [text=cord done=? kids-done=? order=(list pith)]
:: import /lib/feather-icons
/- feather-icons
:: declare that this is a conversion from task to HTMX
:- [%task %$ %htmx]
:: outer gate takes a task, inner gate takes a bowl:neo,
:: so we can access here.bowl and kids.bowl in the ui
|= t=task
|= =bowl:neo
::
:: in this case, all sail rendering
:: happens in helper arms
|^
shell
++ kids ~(tap of:neo kids.bowl)
++ id
::
++ pith-tape
:: pith to tape conversion
|= =pith
^- tape
%- zing
%+ turn (pout here.bowl)
|= smeg=@ta
%+ weld "--"
(trip smeg)
++ form-edit
^- manx
;form.fc.g2.br1
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=task-diff"
=hx-trigger "input changed delay:0.4s from:find textarea, input from:find input"
=hx-swap "none"
=head "edit"
;label.fr.g2.ac.js
;+ =- ?. done.t:+ -
-(a.g [[%checked ""] a.g.-])
^- manx
;input
=type "checkbox"
=name "done"
=onclick (trip 'if (this.checked) { this.setAttribute("checked", "")} else {this.removeAttribute("checked")}')
;
==
;span.grow: Done
;div.htmx-indicator
; ---
==
==
;textarea.wf.p2.border.br1.ma.mono
=name "text"
=autocomplete "off"
=spellcheck "false"
=rows "{<(add 2 (lent (fand ~[10] (trip text.t))))>}"
=oninput "this.setAttribute('value', this.value); this.rows = this.value.split('\\n').length"
=value (trip (@t text.t))
; {(trip (@t text.t))}
==
==
++ form-create
|= [head=@tas label=tape]
^- manx
;div.fc.g1
;button.b0.br1.hover.p2.wfc.mono.f3
=onclick
"""
this.classList.toggle('toggled');
this.nextElementSibling.classList.toggle('hidden');
this.nextElementSibling.firstChild.focus();
"""
; {label}
==
;form.fr.g1.hidden
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=task-diff"
=head (trip head)
=hx-swap "outerHTML"
=hx-target "find button .loading"
;input.wf.p1.border.br1.grow
=name "name"
=autocomplete "off"
=type "text"
=pattern (trip '[a-z]{1}[a-z0-9\\-]+')
=title "lowercase and heps"
=required ""
=placeholder "name"
=oninput "this.setAttribute('value', this.value);"
;
==
;button.b1.br1.hover.p1.wfc.loader
;span.loaded: create
;span.loading: ---
==
==
==
++ part-kid
|= [=pith =idea:neo]
=/ =pail:neo pail.idea
=/ t=task !<(task q.pail)
=- ?. done.t -
-(a.g [[%done ""] a.g.-])
^- manx
;div.fc.g1
=here (en-tape:pith:neo (welp here.bowl pith))
;div.fr.g1
;button
=class "b0 br1 hover p1 tl action mono fr g3 f2"
=style "padding: 4px 9px;"
=type "button"
=onclick
"""
this.textContent = this.textContent === "=" ? "|" : "="; toggleChildren(this);
"""
;span.bold: =
==
;+ =-
=/ that -
=/ classes
%+ weld
"b0 br1 hover p1 grow tl action mono fr g3"
?:(done.t " strike f3" "")
that(a.g [[%class classes] a.g.that])
^- manx
;button
=type "button"
=onclick
"""
this.classList.toggle('toggled'); this.parentNode.nextElementSibling.classList.toggle('hidden');
"""
;span.bold: {(trip -:(pout pith))}
;span.break.f2: {(scag (fall (find [10 ~] (trip text.t)) 30) (trip text.t))}
==
;a
=class "b0 br1 hover p1 loader f3"
=hx-indicator "this"
=href "/neo/hawk{(en-tape:pith:neo here.bowl)}{(en-tape:pith:neo pith)}"
=hx-swap "innerHTML"
;span.loaded: →
;span.loading: .
==
==
;div.border.p2.br1.frw.g2.hidden
=hx-disinherit "hx-indicator"
=style "margin-left: 20px;"
;button.b1.br1.p2.hover
=onclick "this.parentNode.parentNode.parentNode?.insertAdjacentElement('beforeend', this.parentNode.parentNode); center(this);"
; ↧
==
;button.b1.br1.p2.hover
=onclick "this.parentNode.parentNode.nextElementSibling?.insertAdjacentElement('afterend', this.parentNode.parentNode); center(this);"
; ↓
==
;button.b1.br1.p2.hover
=onclick "this.parentNode.parentNode.previousElementSibling?.insertAdjacentElement('beforebegin', this.parentNode.parentNode); center(this);"
; ↑
==
;button.b1.br1.p2.hover
=onclick "this.parentNode.parentNode.parentNode?.insertAdjacentElement('afterbegin', this.parentNode.parentNode); center(this);"
; ↥
==
;div.htmx-indicator.reorder-indicator.p2.f2
; ---
==
;div.basis-full;
;button.b1.br1.p2.hover.loader
=type "button"
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=task-diff"
=hx-swap "outerHTML"
=hx-target "find .loading"
=head "kid-done"
=pith (en-tape:pith:neo pith)
;span.loaded: toggle
;span.loading: ---
==
;button.b1.br1.p2.hover.loader
=type "button"
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=task-diff"
=hx-swap "none"
=hx-on--after-request "this.parentNode.parentNode.remove();"
=head "oust"
=pith (en-tape:pith:neo pith)
;span.loaded: delete
;span.loading: ---
==
;button.b1.br1.p2.hover
=type "button"
=onclick "this.nextElementSibling.classList.toggle('hidden'); this.classList.toggle('toggled');"
; become
==
;div.basis-full.hidden.fr.g1
=hx-post "/neo/hawk{(en-tape:pith:neo (welp here.bowl pith))}?stud=task-diff"
=hx-trigger "become"
=hx-target "find .loading"
=hx-swap "outerHTML"
=head "become"
;input.grow.p2.br1.border
=type "text"
=name "path"
=value (en-tape:pith:neo (welp here.bowl pith))
=oninput "this.setAttribute('value', this.value);"
;
==
;button.p2.b1.hover.br1.loader
=type "button"
=onclick "this.dispatchEvent(new CustomEvent('become', \{composed:true, bubbles: true}))"
;span.loaded: become
;span.loading: ---
==
==
;div.basis-full.p2.pre.mono.scroll-x
; {(trip text.t)}
==
==
;div.fc.g2.hidden
=hx-disinherit "hx-indicator"
=style "margin-left: 20px;"
;
==
==
(en-tape:pith:neo pith)
::
++ shell
::
:: this "shell" div is a wrapper for the whole interface
:: <div class="shell fc js af p2 wf p1 g5 ma" here="{<(pith-tape here.bowl)>}">
;div.shell.fc.js.af.p2.wf.p1.g5.ma
=here (pith-tape here.bowl)
::
:: return a <style> element with
:: the styling in the +css arm
;style: {style}
::
:: embed JavaScript code from +script for
:: a more responsive frontend experience
;+ script
::
:: render from the +task-title arm, which
:: returns a header with the task's title
;+ task-title
:: <div class="fc g1 kids">
;div.fc.g1.kids
::
:: render a <form> element with an ordered list of
:: the child shrubs, which are all the top-level tasks
;+ form-ordered-kids
:: render the <form> that sends a
:: poke to create a new task
;+ form-create
== :: </div>
== :: </div>
::
++ style
^~
%- trip
'''
.shell {
max-width: 650px;
padding-bottom: 50vh;
padding-top: 30px;
}
input[type="text"]:hover {
cursor: text;
}
input:focus {
outline: none;
}
input:checked {
outline: none;
accent-color: black;
}
'''
::
++ script
:: <script>
;script
;+ ;/ %- trip
'''
// applies scrollIntoView() methods to the provided
// to display it as specified
function center(el) {
el.scrollIntoView({
block: "center",
inline: "start",
behavior: "instant"
})
}
function toggleChildren(el) {
let kidsDiv = el.parentNode.nextElementSibling.nextElementSibling;
if (!kidsDiv.children.length) {
let here = el.parentNode.parentNode.getAttribute('here');
let stub = document.createElement("div");
stub.setAttribute("hx-get", `/neo/hawk${here}`);
stub.setAttribute("hx-trigger", 'load');
stub.setAttribute("hx-target", 'this');
stub.setAttribute("hx-select", '.kids');
stub.textContent = "+ + +"
stub.className = "fc as jc"
stub.style = "padding:10px; padding-left: 20px;"
stub.setAttribute("hx-swap", "outerHTML");
kidsDiv.appendChild(stub);
htmx.process(kidsDiv);
}
kidsDiv.classList.toggle('hidden');
}
};
// tell the user why a clicked checkbox
// can't be marked as checked
document.querySelectorAll(".alert").forEach(function(element) {
element.addEventListener('click', function(e) {
if (element.hasAttribute("readonly")){
e.preventDefault();
alert("Subtasks are not completed");
}
})
})
'''
==
== :: </script>
::
++ task-title
^- manx
:: <div class="fc g2 br1">
;div.fc.g2.br1
:: <h2 class="bold.s2.tc">
;h2.bold.s2.tc
; {(trip (@t text.t))}
== :: </h2>
== :: </div>
::
++ kids-check
::
:: check if all subtasks are completed
|= =pith
~& > pith
^- ?
=/ t
!< task
q.q.saga:(~(got of:neo kids.bowl) pith)
~& >> t
kids-done.t
::
::
++ form-ordered-kids
::
:: <form> that keeps track of tasks order, sends %reorder
:: poke if tasks are manually reordered by the user
;form.fc.g1
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=task-diff"
=head "reorder"
=hx-post "/neo/hawk{(pith-tape here.bowl)}?stud=task-diff"
=head "reorder"
=hx-indicator ".reorder-indicator"
=hx-swap "none"
=hx-swap "none"
;*
%+ turn order.t
|= =pith
=/ kid (~(get of:neo kids.bowl) pith)
?~ kid
;div: does not exist {(en-tape:pith:neo pith)}
(part-kid [pith (need kid)])
::;*
::=/ orphans
:: %+ skim kids
:: |= [=pith *]
:: =(~ (find [pith ~] order.t))
::%+ turn
:: %+ sort orphans
:: |= [a=[=pith *] b=[=pith *]]
:: (lth ->.pith.a ->.pith.b)
::part-kid
::
:: iterates over the list of piths in order.task
%+ turn
order.t
|= =pith
:: extract kid information at pith from kids.bowl
:: and runs +part-kid on pith and kid data
=/ kid (~(get of:neo kids.bowl) pith)
?~ kid
;div: does not exist {(pith-tape pith)}
(part-kid [pith (need kid)])
==
++ shell
;div.fc.js.af.p2.wf.p1.g5.ma
=here (en-tape:pith:neo here.bowl)
=style "max-width: 650px; padding-bottom: 50vh; padding-top: 30px;"
;+ script
;+ form-edit
;div.fc.g1.kids
::;+ ?~ (lent order.t) ;/("") (form-create %prep "+")
::;+ ?~ (lent order.t) (form-create %nest "+") ;/("")
;+ form-ordered-kids
::;+ ?~ (lent order.t) ;/("") (form-create %nest "+")
;+ (form-create %nest "+")
::
++ part-kid
::
:: sends %edit poke on input change
|= [=pith =idea:neo]
:: extracts information from idea:neo to task
=/ =pail:neo q.saga.idea
=/ t=task !<(task q.pail)
:: converts pith to tape
=/ pt (pith-tape (welp here.bowl pith))
:: checks if task is done; if so, assigns
:: attribute "done" to the manx created below
=- ?. done.t -
-(a.g [[%done ""] a.g.-])
^- manx
:: <div class="fc g1" here="{<pt>}" done="" ...>
:: toggle hidden attribute on buttons-menu div
;div.fr.g1.p1
=here pt
=onmouseover "this.childNodes[1].classList.remove('hidden');$(this).addClass('b1 br2');"
=onmouseout "this.childNodes[1].classList.add('hidden');$(this).removeClass('b1 br2');"
::
:: div with %edit poke functionality
:: div that sends %edit poke when input with class="text"
:: or input with class "done" are being changed
;div.fr.ac.g1.grow
=hx-post "/neo/hawk{pt}?stud=task-diff"
=hx-trigger "input changed delay:0.4s from:find .text, input from:find .done"
=hx-swap "none"
=head "edit"
;+
:: defines class attribute with class names
=/ class [%class "p2 br1 border done s3"]
=/ class-alert [%class "p2 br1 border done s3 alert"]
::
:: checkbox logic:
:: - if task is toggled, checkbox will
:: appear as checked
:: - if task has kids and all kids are done,
:: user will be free to toggle the task
:: - if task have kids and they are not done,
:: checkbox will have readonly attribute and
:: will show alert onclick
:: - even though we have logic for handling
:: toggling of a parent task, we prevent
:: a task from being marked as done if it
:: has untoggled kids
::
:: combining attribute logic with manx below
=; m
:: checks if the task toggled as done
?. done.t
:: if it's not done, does it have kids?
?~ order.t
::
:: a.g.m is a part of the manx that
:: contains input checkbox attributes;
:: we assign the class attribute to
:: the rest of its data
m(a.g [class a.g.m])
=/ kc
(kids-check pith)
~& >>> kc
?: kc
:: assigning class attribute to
:: the rest of manx data
m(a.g [class a.g.m])
::
:: assigns readonly and class
:: attributes to checkbox; 'alert' class will trigger
:: alert script functionality
m(a.g [[%readonly ""] class-alert a.g.m])
::
:: assigning checked and class attributes
:: to the rest of manx data
m(a.g [[%checked ""] class a.g.m])
^- manx
::
:: onclick logic depending if input
:: has been checked or unchecked:
:: - if checked and doesn't have attribute
:: readonly, adds attribute checked="" to
:: local input and assigns classes "strike f3"
:: to sibling input text
:: - if it's been undone removes attribute checked=""
:: and removes "strike f3" classes from sibling input
::
;input
=type "checkbox"
=name "done"
=onclick (trip 'if (this.checked && !this.hasAttribute("readonly")){ this.setAttribute("checked", "");$(this.nextElementSibling).addClass("strike f3")} else {this.removeAttribute("checked");$(this.nextElementSibling).removeClass("strike f3")}')
;
==
::
:: combining class logic with
:: manx below and make it in to an XML node
;+ =; that
=/ classes
%+ weld
"grow p2 br2 text bold"
?:(done.t " strike f3" "")
:: assigning classes attribute to the manx below
that(a.g [[%class classes] a.g.that])
^- manx
::
:: input has a task text value
:: on input change, it will change the value
:: attribute to update the div's POST request form
;input
=type "text"
=name "text"
=value (trip text.t)
=onclick "$(this).addClass('border br2');$(this).removeClass('bold')"
=onblur "$(this).addClass('bold');$(this).removeClass('border')"
=oninput "this.setAttribute('value', this.value);"
=onmouseover "$(this).addClass('b2 br2');"
=onmouseout "$(this).removeClass('b2');"
;
==
==
:: buttons menu arm is called with pith as input
;+ (buttons-menu pith)
:: a-tag, opens subtask view with spinner logic on loading
;a.p2.br1.hover.action.mono.fr.g2.loader
=hx-indicator "this"
=href "/neo/hawk{(pith-tape here.bowl)}{(pith-tape pith)}"
=hx-swap "innerHTML"
;span.b1.br1.p2.hfc.loaded: →
;span.b1.br1.p2.hfc.loading
;+ loading.feather-icons
==
==
==
::
++ buttons-menu
:: dropdown-menu arm provides reorder
:: and oust(delete) logic for kids
|= =pith
^- manx
::
;div.p2.br1.fr.g2.hidden
=hx-disinherit "hx-indicator"
=style "padding-right:0px;"
::
:: indicator that %reorder POST request is in progress
;div.reorder-indicator.p2.loader
;span.loading.p1
;+ loading.feather-icons
==
==
:: moves kid div one task down and centers view
:: and uses script logic to center user view on moved task
;button.b1.br1.p2.hover.hfc
=onclick "this.parentNode.parentNode.nextElementSibling?.insertAdjacentElement('afterend', this.parentNode.parentNode); center(this);"
; ↓
==
:: moves kid div one task up and centers view
;button.b1.br1.p2.hover.hfc
=onclick "this.parentNode.parentNode.previousElementSibling?.insertAdjacentElement('beforebegin', this.parentNode.parentNode); center(this);"
; ↑
==
:: delete button that sends POST request with %oust poke to parent task
:: and after request been sent removes subtask div from DOM
;button.b1.br1.p2.hover.loader.hfc
=type "button"
=hx-post "/neo/hawk{(pith-tape here.bowl)}?stud=task-diff"
=hx-target "find .loading"
=hx-swap "outerHTML"
=hx-on--after-request "this.parentNode.parentNode.remove();"
=head "oust"
=pith (pith-tape pith)
;span.loaded: delete
;span.loading
;+ loading.feather-icons
==
==
==
::
++ form-create
::
:: form-create arm send POST request with data for %new poke
:: depending on whether it's a parent task or a kid,
:: it specifies a placeholder accordingly
=/ placeholder ?:(?=([%ud @ud] (rear here.bowl)) "subtask" "task")
^- manx
;div.fc.g1.p4r
=style "padding-top:8px;"
:: form for %new poke POST request
;form.fr.g1
=hx-post "/neo/hawk{(pith-tape here.bowl)}?stud=task-diff"
=head "new"
=hx-swap "outerHTML"
=hx-target "find button .loading"
:: by default will append new task to the ordered kid list
;input.hidden
=name "prepend"
=value "no"
;
==
:: input for task text data
;input.wf.p2.border.br2.grow
=name "text"
=autocomplete "off"
=type "text"
=required ""
=placeholder placeholder
=oninput "this.setAttribute('value', this.value);"
;
==
:: button that triggers form to send request
;button.b1.br1.hover.p1.wfc.loader
;span.loaded: create
;span.loading
;+ loading.feather-icons
==
==
==
==
--

View File

@ -0,0 +1,243 @@
/@ todos
/- feather-icons
:- [%todos %$ %htmx]
|= todo=todos
|= =bowl:neo
|^
=/ hide-hid ?:(show-done.todo "" "hide-hidden")
;div
=class "p-page todo-top mw-page ma {hide-hid}"
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=todos"
=hx-swap "none"
=hx-target "this"
=hx-trigger "todo-save, todo-save-name delay:0.4s"
;div.fr.ac.jb.p2.sticky.z1.b0
=style "top: 0;"
;+ indicator
;+ (show-done-toggle show-done.todo)
==
;div.fc.mt1.todo-list(name "todos")
;* (turn todos.todo render-todo)
==
;+ form-new
;div.hidden.template
;+ (render-todo [%.n ''])
==
;+ style
==
++ form-new
%^ add-attr %onsubmit
"""
event.preventDefault();
let template = $(this).closest('.todo-top').find('.template');
let list = $(this).closest('.todo-top').find('.todo-list');
let dolly = template.clone().children().first();
let input = $(this).find('input');
let name = input.val();
input.val('');
dolly.find('.todo-name').text(name);
dolly.find('.edit').val(name);
dolly.find('.edit').attr('value', name);
dolly.appendTo(list);
$(this).emit('todo-save');
"""
;form.fr.g1.mt1.p2
;input.grow.br1.bd1.p2
=required ""
=placeholder "new task"
;
==
;button.p2.b1.br1.bd1.hover
;+ add.feather-icons
==
==
++ indicator
;div.loader.p2
;span.loaded;
;span.loading
;+ loading.feather-icons
==
==
++ show-done-toggle
|= =flag
;label.fr.g2.ac.je.f3.p2
;span.s-1: show done
;+
=; m
?. flag m
m(a.g [[%checked ""] a.g.m])
%^ add-attr %onclick
"""
if($(this).is(':checked')) \{ this.setAttribute('checked', '') } else \{ this.removeAttribute('checked') }
$(this).closest('.todo-top').toggleClass('hide-hidden');
$(this).emit('todo-save');
"""
;input
=type "checkbox"
=name "show-done"
;
==
==
::
++ render-todo
|= [done=? name=@t]
=/ dun ?:(done "done" "")
;div
=class "fc p2 g2 todo {dun}"
;div.fr.ac.jb.g1
;label.fr.g2.ac.js
;+ (checkbox done)
;div.p2.todo-name: {(trip name)}
==
;+ %^ add-attr %oninput
"""
$(this).closest('.todo').find('.todo-name').text(this.value);
$(this).attr('value', this.value);
$(this).emit('todo-save-name');
"""
;input.edit.p2.br1.bd1.grow.hidden
=name "todo-name"
=value (trip name)
;
==
;+ %^ add-attr %onclick
"""
$(this).closest('.todo').find('.tray').toggleClass('hidden');
$(this).closest('.todo').find('.edit').toggleClass('hidden');
$(this).closest('.todo').find('label').toggleClass('hidden');
$(this).toggleClass('toggled');
"""
;button.p2.b0.br1.hover
=type "button"
; ⋮
==
==
;div.fr.ac.je.g2.tray.hidden
;+ %^ add-attr %onclick
"""
let todo = $(this).closest('.todo');
let par = todo.parent();
todo.remove();
par.emit('todo-save');
"""
;button.p-1.b1.br1.bd1.hover
=onclick
; delete
==
;div.grow;
;+ %^ add-attr %onclick
"""
let todo = $(this).closest('.todo');
todo.parent().prepend(todo);
$(this).emit('todo-save');
"""
;button.p-1.b1.br1.bd1.hover
; ⇈
==
;+ %^ add-attr %onclick
"""
let todo = $(this).closest('.todo');
let top = $(this).closest('.todo-top');
if (top.hasClass('hide-hidden')) \{
todo.prevAll(':not(.done)').first().before(todo);
} else \{
todo.prevAll().first().before(todo);
}
$(this).emit('todo-save');
"""
;button.p-1.b1.br1.bd1.hover
; ↑
==
;+ %^ add-attr %onclick
"""
let todo = $(this).closest('.todo');
let top = $(this).closest('.todo-top');
if (top.hasClass('hide-hidden')) \{
todo.nextAll(':not(.done)').first().after(todo);
} else \{
todo.nextAll().first().after(todo);
}
$(this).emit('todo-save');
"""
;button.p-1.b1.br1.bd1.hover
; ↓
==
;+ %^ add-attr %onclick
"""
let todo = $(this).closest('.todo');
todo.parent().append(todo);
$(this).emit('todo-save');
"""
;button.p-1.b1.br1.bd1.hover
; ⇊
==
==
==
::
++ checkbox
|= =flag
=; m
?. flag m
m(a.g [[%checked ""] a.g.m])
%^ add-attr %onclick
"""
if($(this).is(':checked')) \{ this.setAttribute('checked', '') } else \{ this.removeAttribute('checked') };
$(this).closest('.todo').toggleClass('done');
$(this).emit('todo-save');
"""
;input.checkbox-round
=type "checkbox"
=name "done"
;
==
++ style
;style
;+ ;/ %- trip
'''
.checkbox-round {
width: 1.1em;
height: 1.1em;
background-color: var(--b0);
border-radius: 50%;
vertical-align: middle;
border: 1px solid var(--b1);
appearance: none;
-webkit-appearance: none;
outline: none;
cursor: pointer;
}
label {
cursor: pointer;
}
.todo {
border-bottom: 1px solid var(--b1);
}
.todo:last-child {
border: none;
}
.checkbox-round:checked {
background-color: var(--f2);
}
.hide-hidden .done {
display: none;
}
.done .todo-name {
color: var(--f4);
}
'''
==
:: collapses newlines to spaces
++ unline
|= =tape
%+ turn tape
|= =cord
?:(=(cord '\0a') ' ' cord)
++ add-attr
|= [=term =tape =manx]
^- ^manx
%= manx
a.g
:- [term (unline tape)]
a.g.manx
==
--

View File

@ -1,7 +1,8 @@
=/ prelude !>(.)
:- [%vase %$ %htmx]
|= vax=vase
|= =bowl:neo
;div.pre.mono.p2
;+
;/ (of-wall:format (~(win re (sell vax)) 0 80))
^- manx
;pre
; {(of-wall:format (~(win re (sell vax)) 0 80))}
==

View File

@ -0,0 +1,423 @@
# Chapter 1: Counter
One of the simplest shrubs imaginable is a counter that stores one number and takes one poke: make the number go up.
By the end of this chapter you'll understand the structure of a shrub, and how to write a trivial one of your own. This won't explain Shrubbery from first principles — you don't neeed to understand it from first principles to work with it — but you'll see how similar a shrub is to a Gall agent, and where they differ.
You'll also get a glimpse of how one shrub can accomodate various frontend interfaces. We'll make a simple HTMX frontend for Sky, a namespace browser and dev environment.
This chapter is the only real "tutorial" in that Counter doesn't currently exist on your ship. You can build Counter yourself following along this guide. The remaining three chapters will discuss shrubs that already exists in your `%neo` desk: Diary, Messenger, and Tasks.
In the Diary tutorial, you'll see how to write and read data to and from the namepsace. In Messenger, you'll see how shrubs can interact via the dependencies system. In the Tasks chapter, we'll look at how a full-featured UI works in the current system.
This chapter is focused on pattern-matching what you know about Gall to the new system.
## Counter in Gall and Shrubbery
Here's the Gall agent you'll reimplement in Shrubbery. It stores one number and takes one poke, `%inc`, to increment the number.
```
/+ dbug, default-agent, verb
|%
+$ versioned-state
$% state-0
==
+$ state-0
$: %0
value=@ud
==
+$ counter-action
$% [%inc ~]
==
+$ card card:agent:gall
--
::
%+ verb &
%- agent:dbug
=| state-0
=* state -
^- agent:gall
|_ =bowl:gall
+* this .
def ~(. (default-agent this %|) bowl)
::
++ on-init on-init:def
++ on-peek on-peek:def
++ on-watch on-watch:def
++ on-arvo on-arvo:def
++ on-leave on-leave:def
++ on-agent on-agent:def
++ on-fail on-fail:def
++ on-save
!>(state)
::
++ on-load
|= old=vase
^- (quip card _this)
:- ~
%= this
state !<(state-0 old)
==
::
++ on-poke
|= [=mark =vase]
^- (quip card _this)
?+ mark
(on-poke:def mark vase)
::
%noun
=/ act
!<(counter-action vase)
?- -.act
%inc
:- ~
%= this
value +(value)
==
==
==
--
```
Here's the same thing in Shrubery.
```
/@ number
/@ counter-diff
^- kook:neo
|%
++ state
^- curb:neo
[%pro %number]
++ poke
^- (set stud:neo)
(sy %counter-diff ~)
++ deps
^- deps:neo
*deps:neo
++ form
^- form:neo
|_ [=bowl:neo =aeon:neo =stud:neo state-vase=vase]
+* state !<(number state-vase)
++ init
|= old=(unit pail:neo)
^- ((list card:neo) pail:neo)
[~ (need old)]
++ poke
|= [=stud:neo vaz=vase]
^- ((list card:neo) pail:neo)
=/ act
!<(counter-diff vaz)
?> =(-.act %inc)
[~ [%number !>(+(state))]]
--
--
```
## Shrub structure
Let's look at the structure of `/imp/counter`.
```
/@ number
/@ counter-diff
```
These lines import two types from our `/pro` folder: `number` and `counter-diff`. To import from `/pro` we use `/@` as a new Ford-style rune.
A shrub is a five-arm `|%` core — called a `kook:neo` — with an inner two-arm core called a `form:neo`. The `kook` defines type information about the shrub, and the inner `form` contains business logic.
At first glance the `kook` might look familiar to Gall developers, but this is all new logic defining 1) what's stored at this node in the namespace 2) what can be stored below this node, and 3) what we expect to be stored at existing nodes we declare as dependencies.
```
:: $kook:neo
|%
::
:: type this value in the namespace
++ state
!!
::
:: type acceptable requests to
:: change this value in the namespace
++ poke
!!
::
:: constrain the state/pokes of the shrubs that
:: can be created under this shrub in the namespace
++ kids
!!
::
:: declare the state/pokes we expect for existing shrubs
:: whose state we will track, and whose state changes we
:: will react to
++ deps
!!
::
:: handle state changes in this shrub,
:: its kids, and its dependencies
++ form
!!
--
```
The `form` is where the Gall agent-like application logic lives. We only need two arms, which are slightly modified versions of `+on-init` and `+on-poke`.
```
:: $form:neo
|_ [=bowl:neo =aeon:neo =stud:neo state-vase=vase]
::
:: like +on-init, run some logic when this shrub is created
:: unlike +on-init, potentially accept some injected initial state
++ init
|= old=(unit pail:neo)
^- ((list card:neo) pail:neo)
!!
::
:: like +on-poke, run some logic when this shrub is poked
++ poke
|= [=stud:neo vaz=vase]
^- ((list card:neo) pail:neo)
!!
--
```
## Counter logic
Now that we understand the shape of a shrub, let's look at the application logic of the Counter shrub. You can copy the following into the relevant files or type it out for yourself.
There are lots of new types here which are flagged with the `:neo` suffix in code and documentation. We'll cover those in detail in the following chapters.
### /pro/number.hoon
```
,@ud
```
### /pro/counter-diff.hoon
```
,[%inc ~]
```
### /imp/counter.hoon
```
/@ number :: import number type
/@ counter-diff :: import counter-diff type
::
:: outer core
^- kook:neo
|%
::
:: the state of counter is a number
++ state
^- curb:neo
[%pro %number]
::
:: the set of pokes counter takes only contains %counter-diff
:: a stud:neo is like a mark
++ poke
^- (set stud:neo)
(sy %counter-diff ~)
::
:: counter has no dependencies
++ deps
^- deps:neo
*deps:neo
::
:: inner core
++ form
^- form:neo
::
:: the sample is populated with context like bowl, version number, and
:: counter's current state
|_ [=bowl:neo =aeon:neo =stud:neo state-vase=vase]
::
:: de-vase counter's state
+* state !<(number state-vase)
::
:: +init, like +on-init
++ init
::
:: return no cards and the initial given state
:: pail:neo is a (pair stud:neo vase),
:: like a cell of a mark and data
|= old=(unit pail:neo)
^- ((list card:neo) pail:neo)
[~ (need old)]
::
:: +poke, like +on-poke
++ poke
|= [=stud:neo vaz=vase]
^- ((list card:neo) pail:neo)
::
:: de-vase the poke
=/ act
!<(counter-diff vaz)
::
:: crash if we're not incrementing
?> =(-.act %inc)
::
:: return no cards, return a (pair stud:neo vase)
:: where the vase contains the incremented state
[~ [%number !>(+(state))]]
--
--
```
Once you've saved `/imp/counter.hoon` and the `/pro` files, run `|commit %base` and %neo will add it to its state. We can now interact with this shrub in the Dojo.
## Poking the shrub
A `card:neo` is a `(pair pith note)`.
A `pith` is a `(list iota)`, and an `iota` is either a `term` or a head-tagged noun. For instance:
* `/examples/counter/one` would be represented as `~[%examples %counter %one]`.
* `/~sampel/examples/counter/one` would be represented as `~[[%p ~sampel] %examples %counter %one]`.
* `/~sampel/examples/counter/1` would be represented as `~[[%p ~sampel] %examples %counter [%ud 1]]`.
(You might also see a `pith` written in this irregular form `#/[p/our.bowl]/examples/counter/one`.)
Data in Shrubbery is stored by `pith`.
A `note` is one of the four types of command any shrub will accept.
```
+$ note
$% [%make made] :: create a shrub
[%poke =pail] :: poke a shrub
[%tomb cas=(unit case)] :: tombstone a case of the shrub
[%cull ~] :: forward delete
==
```
Lets `%make` a shrub at path `/foo/bar` from the Dojo, giving it an initial state of `0`. Well explain the structure of the `%make` note in more detail in the Diary tutorial.
```
:neo &neo-card [~[[%p our] %foo %bar] [%make %counter `[%number !>(0)] ~]]
```
You should see `>> %make /foo/bar` in the Dojo if successful.
Now we can now send a `%poke` to the counter shrub at this path.
```
:neo &neo-card [~[[%p our] %foo %bar] [%poke [%counter-diff !>([%inc ~])]]]
```
## Counter frontend in Sky
Shrubbery aims to be interface-agnostic. One part of that vision is `/con` files, which make it possible to convert data from one type to another. Here are Counters `/con` files.
### /con/number-htmx.hoon
This converts data stored as the `number` protocol (which is just a `@ud`) to the `htmx` protocol. When you open a shrub in Sky, Sky will attempt to convert its data to the `htmx` type (because Sky includes the [HTMX](https://htmx.org/) library in its frontend) using the appropriate `/con` file. In practice, this means that our `/con` file will take in our shrub's state (and bowl) and output some [Sail](https://docs.urbit.org/language/hoon/guides/sail) that interpolates the `number` in a basic interface consisting of a heading, the number itself, and one button to send an `%inc` poke to the Counter shrub.
```
/@ number :: @ud
:: import /lib/feather-icons (see feather-intro.txt)
/- feather-icons
:: declare that this is a conversion from number to HTMX
:- [%number %$ %htmx]
::
:: this gate accepts a number and
:: a gate that accepts a bowl:neo;
:: we'll use bowl:neo to get the
:: here.bowl of the shrub that's using this /con file
|= =number
|= =bowl:neo
::
:: this gate returns a manx, which is what Hoon uses
:: to store dynamic XML nodes; in this case we'll use
:: Sail to specify a manx that expects the HTMX library
:: to be available on the frontend
^- manx
::
:: open a <div class="p3 fc g2 ac br2">
:: these utility classes are specified in feather.css,
:: which this /con file expects on the frontend
;div.p3.fc.g2.ac.br2
:: <h1>Counter</h1>
;h1: Counter
:: <p>{number}</p>
;p: {<number>}
:: open a <form> with HTMX attributes
;form
::
:: hx-post will issue a POST request to the provided
:: url and swap the response into the DOM
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=counter-diff"
::
:: hx-target specifies the target for hx-post's DOM
:: swap: the element with class "loading"
=hx-target "find .loading"
::
:: hx-swap specifies how the response to hx-post's
:: request will be swapped in relative to the target
=hx-swap "outerHTML"
::
:: here, the head attribute specifies the poke that
:: hx-post will send to the target shrub; look at
:: /con/node-counter-diff.hoon for more on =head
=head "inc"
::
:: below, the classes "loaded", "loader", and
:: "loading" provide loading spinner behavior on
:: sending and receiving this form's POST request
::
:: <button class="bd1 br1 pr b1 hover loader">
;button.bd1.br1.p2.b1.hover.loader
:: <span class="loaded">Increment</span>
;span.loaded: Increment
:: <span class="loading">
;span.loading
:: import +loading sail from /lib/feather-icons
;+ loading.feather-icons
== :: </span>
== :: </button>
== :: </form>
== :: </div>
```
### /con/node-counter-diff.hoon
This is a more straightforward conversion from a dynamic XML node (in this case, HTMX), to a `%counter-diff`. Using a modified version of the [manx-utils](https://github.com/tinnus-napbus/manx-utils) Hoon library for brevity, we extract the XML nodes `head` attribute and use that to form the `%counter-diff`, which is `[%inc ~]`.
```
/@ node :: manx
/@ counter-diff :: [%inc ~]
:: import /lib/manx-utils, which helps us work with XML
/- manx-utils
:: declare this is a conversion from node to counter-diff
:- [%node %$ %counter-diff]
|= =node
^- counter-diff
:: initiate the manx-utils door with node
=/ mu ~(. manx-utils node)
::
:: got:mu gets an attribute from the manx by its name
:: in this case, the =head specified in /con/number-htmx
:: we expect the head from the manx to be %inc,
:: but we could add more terms to that type union...
=/ head (?(%inc) (got:mu %head))
::
:: return the [%inc ~] poke
[head ~]
```
## Testing the Counter in Sky
The Sky homepage shows you one tile for all of the shrubs who are the immediate children of your `/home` shurb, which was made for you upon booting `%neo` for the first time. You wont see a Counter tile there because there is no `/counter` shrub beneath `/home`, so lets make one.
```
:neo &neo-card [~[[%p our] %home %counter] [%make %counter `[%number !>(0)] ~]]
```
If you refresh your browser you should now see a tile labelled “counter”. Click there to see the Counter frontend from the `/con` file and increment the state of the `/counter` shrub.
## Building on the Counter
You should now be able to make some minor changes to the counter example above. Try the following:
- Initialize the shrub with a default state if the given `(unit vase)` in `+init` is empty.
- Add pokes for `%dec`, `%add`, and `%sub`.

View File

@ -0,0 +1,308 @@
# Chapter 2: Diary
Now that you understand the structure of a shrub, the natural next step is to look at a shrub with kids.
Unlike the Counter example, there is no equivalent way to implement `/imp/diary` in Gall. Were now going to work directly with Urbits programmable, global, referentially transparent namespace as a tool to read and write data.
## /imp/diary
Diary is an app that lets you write timestamped diary entries that the frontend will show in a chronological feed. Heres a succinct version of the apps backend logic, which clocks in at 34 lines of code.
```
:: /imp/diary.hoon
/@ txt :: @t
/@ diary :: name=@t
/@ diary-diff :: ?([%put-entry id=@da =txt] [%del-entry id=@da])
^- kook:neo
|%
++ state only/%diary
++ poke (sy %diary-diff ~)
++ kids `y/(~(gas by *lads:neo) ~[[[|/%da |] [only/%txt ~]]])
++ deps *deps:neo
++ form
^- form:neo
|_ [=bowl:neo =aeon:neo =pail:neo]
++ init
|= old=(unit pail:neo)
^- (quip card:neo pail:neo)
[~ ?~(old diary/!>(*diary) u.old)]
++ poke
|= [=stud:neo vax=vase]
^- (quip card:neo pail:neo)
?> =(%diary-diff stud)
=/ state !<(diary q.pail)
=/ act !<(diary-diff vax)
?> =(our ship.src):bowl
?- -.act
%put-entry
:_ diary/!>(state)
~[[(welp here.bowl ~[da/id.act]) [%make %txt `txt/!>(txt.act) ~]]]
%del-entry
:_ diary/!>(state)
~[[(welp here.bowl ~[da/id.act]) [%tomb ~]]]
==
--
--
```
Most of this should be legible after the first tutorial. The only new ideas are the `+kids` arm, the use of `bowl`, and `%tomb`.
## +kids
Every shrub is a node in one tree, with one “root shrub” at the top of that tree. Every shrub below the root shrub is either one of its immediate children or one of its descendants.
In its `+kids` arm, every shrub can define constraints for the shrubs below it in the namespace, whether thats constraining the types of their state or the pokes theyll accept from other shrubs.
Lets expand on diarys `+kids` arm.
```
:: /imp/diary.hoon
::
:: constrain shrubs below diary in the namespace
:: by defining the types of their state and pokes
++ kids
::
:: kids:neo is a (unit port:neo)
^- kids:neo
%- some
::
:: port:neo is (pair dare:neo lads:neo)
:: dare:neo is ?(%y %z)
:: if %y, only constrain our immediate children
:: if %z, recursively constrain all descendants
:- %y
:: lads:neo is (map pish:neo lash:neo)
%- ~(gas by *lads:neo)
:~ :- ::
:: pish:neo
:: to simplify: [%.n @da] means the kid's
:: path contains any @da, and %.n is there
:: to signify that the pith can not have more
:: fields afterwards
[[%.n %da] %.n]
::
:: lash:neo is (pair curb:neo (set stud:neo))
:: curb:neo defines the kids' state
:: (set stud:neo) defines the kids' pokes
[[%only %txt] ~]
==
```
In this case, Diary only constrains its immediate children. Each of its children lives at `/path/to/diary/<@da>`, where `<@da>` is used as an ID for the entry, and those children are just `%txt`s (cords) which store the text of the diary entry.
Theres no need for Diary to recursively constrain every single path that nests under `/path/to/diary`, since the app only needs to reserve the first “generation” of shrubs below it in order to work. In this case, the developer has forbidden any shrubs being created under the diary entries by ending the `pish` in a `%.n`. If that were `%.y`, any app could make a shrub at `/path/to/diary/<@da>/comments`. or `/path/to/diary/<@da>/backlinks`.
To define the shrubs kids, we create an empty map which is the bunt of `lads:neo`, then use `+gas:by` to populate the map with our own data, which will be a `(list (pair pish:neo lash:neo))`.
The `pish:neo` statically types the paths which well allow to be created beneath this shrub, and the `lash:neo` defines the kids state and pokes. In this case, the kids take no pokes and their state can only be `%txt`.
(Its worth flagging that `$curb:neo` contains several combinatorial rules about state types. For example `[%pro %txt]` would mean “the state of the child shrub can be any type which is readily convertible into a `%txt` with a conversion we have available in the `/con` folder”. This gives us a clue as to how we could handle state transitions and interoperability over the lifetime of our shrub, but its outside the remit of this tutorial.)
## bowl:neo
Notice that the `src` in `bowl:neo` differs from `bowl:gall`. Heres the new type in full.
```
:: /sur/neo.hoon
+$ bowl
$: src=[=ship =pith] :: a request's source ship and shrub
our=@p :: our ship
here=pith :: path to this shrub
now=@da :: current datetime
eny=@uvJ :: entropy
deps=(map term (pair pith lore)) :: dependencies
kids=lore :: our kids' state
==
```
## Generating cards, tombstoning shrubs
We covered `card:neo` in the Counter tutorial, but this is the first time were seeing one generated within a shrub. Diary takes two pokes: `%put-entry`, to create a new diary entry, and `%del-entry` to tombstone one.
Heres the `+poke` arm of the Diary shrub, expanded with comments.
```
:: /imp/diary.hoon
++ poke
|= [=stud:neo vax=vase]
^- (quip card:neo pail:neo)
?> =(%diary p.pail)
?> =(%diary-diff stud)
=/ state !<(diary q.pail)
=/ act !<(diary-diff vax)
::
:: assert the poke comes from our ship
:: src.bowl:neo is (pair ship pith)
?> =(our ship.src):bowl
?- -.act
%put-entry
:: return unchanged state
:_ [%diary !>(state)]
::
:: create list of one card:neo
:: card:neo is (pair pith:neo note:neo)
:~ :- %+ welp
::
:: here.bowl is the path of this shrub
:: /path/to/diary
here.bowl
::
:: append post id
:: /path/to/diary/~2024.6.3..14.07.15..7098
~[[%da id.act]]
::
:: this note will %make a new shrub
:: at the pith we defined above
^- note:neo
:: [%make stud:neo (unit pail:neo) conf:neo]
:* %make
::
:: new shrub has the implementation %txt
:: see /imp/txt.hoon, a stub that allows
:: you to create a %txt in the namespace
%txt
::
:: new shrub's initial state
:: is the text from the poke
`[%txt !>(txt.act)]
::
:: conf:neo is (map term pith:neo)
:: declare this new shrub's dependencies
:: which are also shrubs; diary has none
~
==
==
::
%del-entry
:: return unchanged state
:_ [%diary !>(state)]
::
:: send a %tomb note to /path/to/diary/<id>
:: this will tombstone the diary entry,
:: effectively deleting it from the namespace
:~ :- %+ welp
here.bowl
~[[%da id.act]]
^- note:neo
[%tomb ~]
==
==
```
The above is mostly self-explanatory, but its worth emphasizing a few points.
Diarys state never changes. Its state is just its name, a `@t`. It stores all its entries in the namespace. This makes every one of them amenable to being scried out by other apps, and it leaves room for other apps to make shrubs beneath `/path/to/diary/<@da>` at paths like `/<@da>/comments`, `/<@da>/reacts`, or `/<@da>/backlinks`.
The location of this shrub, `here.bowl`, is just a list. So its easy to `+welp` paths like `/<@da>` or `/<@da>/comments` onto the end. In this context, just keep in mind that they need to be `iota`s like `[%da now.bowl]`.
The `%make` card has two mysteries: it initializes diary entries with an empty `%txt` implementation and an empty map of dependencies. Well punt on dependencies until the next tutorial. If you look at `/imp/txt.hoon`, its just a `~`. This allows us to shove `%txt`s into the namespace; if the `/imp/<foo>` file is just a `~`, `/app/neo` will notice this and substitute in a shrub that takes the state of the type defined in `/pro/<foo>`. Like everything else in these tutorials this is subject to change!
## Diary frontend
Like Counter, the Diary shrub just has two `/con` files to convert to and from an HTMX frontend within the Sky browser.
```
:: /con/diary-htmx.hoon
/@ diary :: name=@t
/- feather-icons
:- [%diary %$ %htmx]
|= dia=diary
|= =bowl:neo
^- manx
|^
;div.p2
=label "Diary"
;div.ma.fc.g2
=style "max-width: 650px;"
;+ form-put-entry
;*
%+ turn
~
link-entry
==
==
++ form-put-entry
;form.fc.g2
=style "margin-bottom: 30px;"
=hx-post "{(en-tape:pith:neo :(weld /neo/hawk here.bowl))}?stud=diary-diff"
=hx-on-submit "this.reset()"
=hx-target "this"
=hx-swap "afterend"
=head "put-entry"
;date-now(name "id");
;textarea.p2.bd1.br1
=name "text"
=placeholder "today, i ..."
=oninput "this.setAttribute('value', this.value)"
=rows "5"
=required ""
=autocomplete "off"
;
==
;button.p2.b1.br1.bd1.wfc.hover.loader
;span.loaded.s2: create
;span.loading
;+ loading.feather-icons
==
==
==
++ link-entry
|= [pax=pith =pail:neo]
=/ tape (trip !<(@t q.pail))
=/ subject-end (fall (find [10]~ tape) 56)
=/ subject (scag subject-end tape)
=/ id (trip (snag 0 (pout pax)))
;div.fr.g2
;a.p2.br1.grow.b1.hover.loader
=href "{(en-tape:pith:neo (weld /neo/hawk here.bowl))}/{id}"
;div.loaded.fc.g1.js.as.g2
;span.f3: {(pretty-date `@da`->:pax)}
;span.bold: {subject}
==
;span.loading
;+ loading.feather-icons
==
==
;button.p2.br1.fr.g2.b1.hover.fc.ac.jc.loader
=onclick "alert('not yet implemented. no tombstoning?')"
;span.loaded
;+ close.feather-icons
==
;span.loading
;+ loading.feather-icons
==
==
==
++ pretty-date
|= date=@da
^- tape
=/ d (yore date)
"{(y-co:co y:d)}-{(y-co:co m:d)}-{(y-co:co d:t:d)}"
--
```
Theres only a bit more going on here than in Counters `/con/number-htmx.hoon` file. The main thing is that we have `+link-entry` and `+pretty-date` helper arms which are being used like functional frontend components.
The Diary frontend is a text box at the top with a list of entries generated from a list of the Diary shrubs child `pith`s. Nowhere is a list of children in the state being passed from the backend to the frontend, like you might see with a Gall agent passing converting a `(list item)` to a JSON array through a mark in the event that a frontend has scried or subscribed on a path, all of which would require painstaking specification by the developer.
```
:: /con/node-diary-diff.hoon
/@ node :: manx
/@ diary-diff :: ?([%put-entry id=@da txt=@t] [%del-entry id=@da])
/- manx-utils
:- [%node %$ %diary-diff]
|= nod=node
^- diary-diff
=/ mu ~(. manx-utils nod)
=/ head (@tas (got:mu %head))
=/ id (slav %da (vol:mu "id"))
=/ text (vol:mu "text")
[%put-entry id text]
```
Just like the `/con/node-counter-diff.hoon` in the Counter tutorial, all this does is extract attributes from an XML node, having been converted into a `manx`, and manually constructs the poke expected by the shrub.
## Building on Diary
- Amend the `+kids` arm so that other apps could create shrubs like `/path/to/diary/<@da>/comments`.
- Diary takes two pokes: `%put-entry` and `%del-entry`, but only one of these is supported on the frontend. Implement a `%del-entry` poke from the frontend.

View File

@ -0,0 +1,691 @@
# Intro to Feather
Feather is a _Design System_ which gives
Sail developers easy access to a small set of
predefined styles.
Feather is implemented as a library of CSS classes,
and is bundled with Sky and Hawk.
Benefits of opting into
Feather's constraints:
- Feather uses CSS variables that...
- the user can override
- respect OS light/dark theme
- UIs styled with Feather fit in
with the style of Sky and Hawk
- "birds of a feather flock together"
-----------------------------------------------
## The `prose` Class
the `.prose` class adds blog-like spacing,
sizing, and readability styling to semantic
children such as `p`
`li` `h1` `h2` `a` etc.
This document uses it.
```
;article.prose
# cool
- neat
- fine
==
```
-----------------------------------------------
## Flexbox
;*
%+ turn
:~
"fr jb"
"fr ja"
"fr jc"
"fr js"
"fr je"
"fc ac"
"fc as"
"fc ae"
"fc af"
==
|= =tape
;div.fc.g1.br1(style "margin-top: 20px;")
;code: {tape}
;div
=class "bd1 br1 p3 g1 {tape}"
;* %+ turn (gulf 1 3)
|= n=@
;b.b1.f3.p1.br1: X
==
==
;div.fc.g3(style "margin-top: 50px;")
;*
%+ turn
:~
"fr g0"
"fr g1"
"fr g2"
"fr g3"
"fr g4"
"fr g5"
"fr g6"
"fr g7"
"fr g8"
==
|= =tape
;div
=class "s-2 mono {tape}"
;div.bd1.p2: {tape}
;* %+ turn (gulf 1 3)
|= n=@
;div.bd1.p2;
==
==
;div.fr.g2(style "margin-top: 50px;")
;*
%+ turn
:~
"fc g0"
"fc g1"
"fc g2"
"fc g3"
"fc g4"
"fc g5"
"fc g6"
"fc g7"
"fc g8"
==
|= =tape
;div
=class "s-2 mono {tape}"
;div.bd1.p1(style "width: min-content;"): {tape}
;* %+ turn (gulf 1 3)
|= n=@
;div.bd1.p2;
==
==
;div.frw.g2(style "margin-top: 50px;")
;*
%+ turn (gulf 1 10)
|= n=@
;div.p2.bd1.mono: frw g2
==
;div.f2.mono
;div.fr.g4.p2.bd1.br1(style "margin-top: 50px;")
;div.p2.br1.b1;
;div.p2.br1.b1.tc.grow: grow
;div.p2.br1.b1;
==
;div.fr.g4.p2.bd1.br1(style "margin-top: 20px;")
;div.p2.br1.b1;
;div.p2.br1.b1;
;div.p2.br1.b1.tc.grow: grow
==
;div.fr.g4.p2.bd1.br1(style "margin-top: 20px;")
;div.p2.br1.b1.tc.grow: grow
;div.p2.br1.b1;
;div.p2.br1.b1.tc.grow: grow
==
==
-----------------------------------------------
## Typography
;div.frw.g1.br1.ac.jc
;*
%+ turn
:~
"s-2"
"s-1"
"s0"
"s2"
"s3"
"s4"
"s5"
"s6"
==
|= =tape
;span
=class "p2 {tape}"
; {tape}
==
==
;div.frw.g1.br1.ac.jc
;*
%+ turn
:~
"mono"
"bold"
"italic"
"underline"
"strike"
==
|= =tape
;span
=class "p2 {tape}"
; {tape}
==
==
-----------------------------------------------
## Text Alignment
;*
%+ turn
:~
"tl"
"tc"
"tr"
==
|= =tape
;div
=class "mono {tape}"
; {tape}
==
-----------------------------------------------
## Foreground & Background Colors
;div.frw.g1.br1.ac.jc.mono
;*
%+ turn
:~
"f-3"
"f-2"
"f-1"
"f0"
"f1"
"f2"
"f3"
"f4"
==
|= =tape
;span
=class "p2 bold {tape}"
; {tape}
==
==
;div.frw.g1.br1.ac.jc.mono
;*
%+ turn
:~
"b-3"
"b-2"
"b-1"
"b0"
"b1"
"b2"
"b3"
"b4"
==
|= =tape
;span
=class "p2 {tape}"
; {tape}
==
==
-----------------------------------------------
## Padding
;div.fc.ac.g2
;*
%+ turn
:~
"p-8"
"p-7"
"p-6"
"p-5"
"p-4"
"p-3"
"p-2"
"p-1"
"p0"
"p1"
"p2"
"p3"
"p4"
"p5"
"p6"
"p7"
"p8"
"p-page"
==
|= =tape
;div
=class "wfc mono f2 b1 {tape}"
; {tape}
==
==
-----------------------------------------------
## Margin
;div
;*
%+ turn
:~
"m0"
"ma"
"mt1"
"mt2"
"mt3"
==
|= =tape
;div
=class "mono f2 b2 br1 wfc p2 {tape}"
; {tape}
==
==
-----------------------------------------------
## Opacity
;div.frw.g2.ac.jc
;*
%+ turn
:~
"o0"
"o1"
"o2"
"o3"
"o4"
"o5"
"o6"
"o7"
"o8"
"o9"
"o10"
==
|= =tape
;div.fc.g1.ac.jc
;span.mono.s-1.f2: {tape}
;div
=class "wfc mono f0 b1 p4 bd1 {tape}"
;
==
==
==
-----------------------------------------------
## Borders
;div.frw.ac.g2(style "margin-top: 20px;")
;*
%+ turn
:~
"bd0"
"bd1"
"bd2"
"bd3"
==
|= =tape
;div
=class "wfc mono f2 p2 {tape}"
; {tape}
==
==
;div.frw.ac.g2(style "margin-top: 20px;")
;*
%+ turn
:~
"br0"
"br1"
"br2"
"br3"
==
|= =tape
;div
=class "wfc mono f2 p2 bd1 {tape}"
; {tape}
==
==
-----------------------------------------------
## Dimensions
- `wf` width: full
- `wfc` width: fit-content
- `mw-page` max-width: page (650px)
- `hf` height: full
- `hfc` height: fit-content
-----------------------------------------------
## Special
;*
%+ turn
:~
"toggled"
"hover"
==
|= =tape
;button(style "margin: 5px;")
=class "wfc mono f0 b0 p2 bd1 {tape}"
; {tape}
==
the `hidden` class hides the element.
-----------------------------------------------
## Request indicators
`loader` indicator parent
`loading` loading state
`loaded` loaded state
;details.mt1
;summary: Example
;button.mt1.b1.p1.hover.br1.bd1.f1.loader
=onclick "$(this).toggleClass('htmx-request')"
;span.loaded: toggle load state
;span.loading.f1: ...loading
==
```
;button.loader
=onclick "$(this).toggleClass('htmx-request')"
;span.loaded: toggle load state
;span.loading: ...loading
==
```
==
-----------------------------------------------
## Misc
`scroll-y`\
scroll-y: auto
`scroll-x`\
scroll-x: auto
`relative`\
position: relative
`sticky`\
position: sticky
`absolute`\
position: absolute
`fixed`\
postion: fixed
`block`\
display: block
`inline`\
display: inline-block
`pre`\
white-space: pre
`pre-line`\
white-space: pre-line
`break`\
word-break: break-word
`action`\
touch-action: manipulation
-----------------------------------------------
## Feather Sutra
> Order requires constraints.
### Primary Constraint
Interfaces must work on a mobile web browser.
### Consequent Constraints
- no element should need to be wider than 350px
- avoid horizontal scrolling
- left-click is the only mouse event
### Locality of Behavior
- co-locate actionable elements with the elements on which they act
- put styling inline
- put javascript inline
### Mechanical Simplicity
- don't fight the browser
- avoid animations
- store state as text attributes in the DOM, not javascript state
### UX low-hanging fruit
- network requests should always have indicators
- prefer autosave to manually saving
- be semantic with your tags
-----------------------------------------------
## Feather Tantra
> Life evolves through a vital chaos.
```
;div.any.class(style "any: css;");
;div
=class "any class"
=style
"""
any: "css";
that-you: "want;
"""
;
==
;style
;+ ;/ %- trip
'''
.any-css {
that-you: "want";
}
'''
==
;script
;+ ;/ %- trip
'''
// any javascript you want
'''
==
```
;style
;+ ;/ %- trip
'''
details {
display: flex !important;
flex-direction: column;
padding: 12px;
background: var(--b1);
box-sizing: border-box;
}
hr {
margin: 100px 0;
}
'''
==

Some files were not shown because too many files have changed in this diff Show More