31 Commits

Author SHA1 Message Date
cuddle-please
61090ba5c2 chore(release): 0.2.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-05-25 19:18:45 +00:00
a70f1cd4b4 chore(deps): update all dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-25 19:15:42 +00:00
0927d36505 fix(deps): update rust crate serde to v1.0.203
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-25 18:41:46 +00:00
bc2b47a6c5 fix(deps): update rust crate prost to 0.12.6
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-17 16:23:23 +00:00
44d9ed2790 chore(deps): update rust crate itertools to 0.13.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-16 16:29:20 +00:00
118aeb3898 fix(deps): update rust crate prost to 0.12.5
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-16 14:31:21 +00:00
65c2466f97 feat: enable creating items on the same level
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-16 10:10:11 +02:00
832587b51d feat: add command for quickly creating an item
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-15 15:25:55 +02:00
4ad8120cb5 feat: remove removal of spaces in title
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-15 15:17:24 +02:00
1f0f526e38 chore: move unused imports into cfg
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-15 15:12:10 +02:00
91ad8eda01 chore: remove unused functions and fix warnings
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-15 15:10:35 +02:00
7496d0e964 chore: remove unused variables
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-15 15:07:49 +02:00
208b14583e chore: fix formatting
Some checks failed
continuous-integration/drone/push Build is failing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-15 15:07:20 +02:00
4a91a564bf feat: with toggle item
Some checks failed
continuous-integration/drone/push Build is failing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-15 15:06:16 +02:00
364f3992bb fix(deps): update rust crate serde to 1.0.202
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-15 08:07:52 +00:00
837caee5db feat: with backend
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-14 23:30:20 +02:00
816869e6f9 feat: can get actual available roots
Some checks failed
continuous-integration/drone/push Build is failing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-14 20:58:38 +02:00
7bdf8393b1 feat: can add items
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-13 23:33:37 +02:00
699bac7159 feat: server can actually create root and sections
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-13 22:57:20 +02:00
76f1c87663 feat: abstract commander
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-12 22:24:37 +02:00
64d59e069f chore: refactor out graph created event
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-12 21:11:08 +02:00
9bb5bc9e87 feat: with async commands instead of inline mutations phew.
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-12 21:07:21 +02:00
2d63d3ad4c refactor: let state use either local or backend
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-12 15:54:03 +02:00
9cb3296cec chore: remove warnings
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-12 14:38:03 +02:00
874045dca8 chore: remove extra logs
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-12 14:35:35 +02:00
5548d8e36e feat: add command pattern
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-12 14:29:14 +02:00
cf26422673 feat: allow async function in command
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-12 12:58:54 +02:00
4a0fcd1bbb feat: move core to tui and begin grpc work
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-11 23:23:00 +02:00
86cba91b16 feat: add protos
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-11 16:23:52 +02:00
113c646334 feat: update deps
Some checks failed
continuous-integration/drone/push Build is failing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-11 15:51:22 +02:00
6a147ba0d2 feat: with basic server
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-05-11 15:51:01 +02:00
66 changed files with 4436 additions and 976 deletions

View File

@@ -6,6 +6,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.2.0] - 2024-05-25
### Added
- enable creating items on the same level
- add command for quickly creating an item
- remove removal of spaces in title
- with toggle item
- with backend
- can get actual available roots
- can add items
- server can actually create root and sections
- abstract commander
- with async commands instead of inline mutations phew.
- add command pattern
- allow async function in command
- move core to tui and begin grpc work
- add protos
- update deps
- with basic server
### Fixed
- *(deps)* update rust crate serde to v1.0.203
- *(deps)* update rust crate prost to 0.12.6
- *(deps)* update rust crate prost to 0.12.5
- *(deps)* update rust crate serde to 1.0.202
### Other
- *(deps)* update all dependencies
- *(deps)* update rust crate itertools to 0.13.0
- move unused imports into cfg
- remove unused functions and fix warnings
- remove unused variables
- fix formatting
- refactor out graph created event
- let state use either local or backend
- remove warnings
- remove extra logs
## [0.1.0] - 2024-05-11
### Added

1177
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,8 @@ resolver = "2"
[workspace.dependencies]
hyperlog-core = { path = "crates/hyperlog-core" }
hyperlog-tui = { path = "crates/hyperlog-tui" }
hyperlog-server = { path = "crates/hyperlog-server" }
hyperlog-protos = { path = "crates/hyperlog-protos" }
anyhow = { version = "1" }
tokio = { version = "1", features = ["full"] }
@@ -13,8 +15,12 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
clap = { version = "4", features = ["derive", "env"] }
dotenv = { version = "0.15" }
axum = { version = "0.7" }
serde = { version = "1.0.202", features = ["derive"] }
serde_json = "1.0.117"
itertools = "0.12.1"
itertools = "0.13.0"
uuid = { version = "1.8.0", features = ["v4"] }
tonic = "0.11.0"
futures = { version = "0.3.30" }
[workspace.package]
version = "0.1.0"
version = "0.2.0"

View File

@@ -12,19 +12,9 @@ clap.workspace = true
dotenv.workspace = true
axum.workspace = true
serde = { version = "1.0.201", features = ["derive"] }
sqlx = { version = "0.7.4", features = [
"runtime-tokio",
"tls-rustls",
"postgres",
"uuid",
"time",
] }
serde = { version = "1.0.202", features = ["derive"] }
uuid = { version = "1.8.0", features = ["v4"] }
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
serde_json = "1.0.117"
bus = "2.4.1"
dirs = "5.0.1"
[dev-dependencies]
similar-asserts = "1.5.0"

View File

@@ -1,11 +1 @@
#![feature(map_try_insert)]
pub mod commander;
pub mod querier;
pub mod engine;
pub mod events;
pub mod log;
pub mod shared_engine;
pub mod state;
pub mod storage;

View File

@@ -1,32 +0,0 @@
use crate::{
commander::Commander, events::Events, querier::Querier, shared_engine::SharedEngine,
storage::Storage,
};
#[allow(dead_code)]
pub struct State {
engine: SharedEngine,
pub storage: Storage,
events: Events,
pub commander: Commander,
pub querier: Querier,
}
impl State {
pub fn new() -> anyhow::Result<Self> {
let storage = Storage::new();
let engine = storage.load()?;
let events = Events::default();
let engine = SharedEngine::from(engine);
Ok(Self {
engine: engine.clone(),
storage: storage.clone(),
events: events.clone(),
commander: Commander::new(engine.clone(), storage, events)?,
querier: Querier::new(engine),
})
}
}

View File

@@ -0,0 +1,12 @@
[package]
name = "hyperlog-protos"
edition = "2021"
version.workspace = true
[dependencies]
tonic.workspace = true
prost = "0.12.6"
[build-dependencies]
tonic-build = "0.11.0"

View File

@@ -0,0 +1,3 @@
fn main() {
tonic_build::compile_protos("proto/hyperlog.proto").unwrap();
}

View File

@@ -0,0 +1,92 @@
syntax = "proto3";
package hyperlog;
message UserGraphItem {
map<string, GraphItem> items = 1;
}
message SectionGraphItem {
map<string, GraphItem> items = 1;
}
message ItemStateNotDone {}
message ItemStateDone {}
message ItemGraphItem {
string title = 1;
string description = 2;
oneof item_state {
ItemStateNotDone not_done = 3;
ItemStateDone done = 4;
}
}
message GraphItem {
oneof contents {
UserGraphItem user = 1;
SectionGraphItem section = 2;
ItemGraphItem item = 3;
}
}
service Graph {
// Commands
rpc CreateSection(CreateSectionRequest) returns (CreateSectionResponse);
rpc CreateRoot(CreateRootRequest) returns (CreateRootResponse);
rpc CreateItem(CreateItemRequest) returns (CreateItemResponse);
rpc UpdateItem(UpdateItemRequest) returns (UpdateItemResponse);
rpc ToggleItem(ToggleItemRequest) returns (ToggleItemResponse);
// Queriers
rpc GetAvailableRoots(GetAvailableRootsRequest) returns (GetAvailableRootsResponse);
rpc Get(GetRequest) returns (GetReply);
}
// Commands
message CreateSectionRequest {
string root = 1;
repeated string path = 2;
}
message CreateSectionResponse {}
message CreateRootRequest {
string root = 1;
}
message CreateRootResponse {}
message CreateItemRequest {
string root = 1;
repeated string path = 2;
ItemGraphItem item = 3;
}
message CreateItemResponse {}
message UpdateItemRequest {
string root = 1;
repeated string path = 2;
ItemGraphItem item = 3;
}
message UpdateItemResponse {}
message ToggleItemRequest {
string root = 1;
repeated string path = 2;
}
message ToggleItemResponse {}
// Queries
message GetAvailableRootsRequest {}
message GetAvailableRootsResponse {
repeated string roots = 1;
}
message GetRequest {
string root = 1;
repeated string paths = 2;
}
message GetReply {
GraphItem item = 1;
}

View File

@@ -0,0 +1,3 @@
pub mod hyperlog {
tonic::include_proto!("hyperlog"); // Specify the same package name as in your .proto file
}

View File

@@ -0,0 +1,30 @@
[package]
name = "hyperlog-server"
version = "0.1.0"
edition = "2021"
[dependencies]
hyperlog-core.workspace = true
hyperlog-protos.workspace = true
anyhow.workspace = true
tokio.workspace = true
tracing.workspace = true
axum.workspace = true
serde.workspace = true
serde_json.workspace = true
uuid.workspace = true
tonic.workspace = true
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
sqlx = { version = "0.7.4", features = [
"runtime-tokio",
"tls-rustls",
"postgres",
"uuid",
"time",
] }
[dev-dependencies]
similar-asserts = "1.5.0"
tempfile = "3.10.1"

View File

@@ -0,0 +1,17 @@
-- Add migration script here
CREATE TABLE roots (
id UUID NOT NULL PRIMARY KEY,
root_name VARCHAR(255) UNIQUE NOT NULL
);
CREATE TABLE nodes (
id UUID NOT NULL PRIMARY KEY,
root_id UUID NOT NULL,
path VARCHAR NOT NULL,
item_type VARCHAR NOT NULL,
item_content JSONB
);
CREATE UNIQUE INDEX idx_unique_root_path ON nodes(root_id, path);

View File

@@ -0,0 +1,154 @@
use hyperlog_core::log::ItemState;
use crate::{
services::{
create_item::{self, CreateItem, CreateItemExt},
create_root::{self, CreateRoot, CreateRootExt},
create_section::{self, CreateSection, CreateSectionExt},
toggle_item::{self, ToggleItem, ToggleItemExt},
update_item::{self, UpdateItem, UpdateItemExt},
},
state::SharedState,
};
#[allow(dead_code)]
pub enum Command {
CreateRoot {
root: String,
},
CreateSection {
root: String,
path: Vec<String>,
},
CreateItem {
root: String,
path: Vec<String>,
title: String,
description: String,
state: ItemState,
},
UpdateItem {
root: String,
path: Vec<String>,
title: String,
description: String,
state: ItemState,
},
ToggleItem {
root: String,
path: Vec<String>,
},
Move {
root: String,
src: Vec<String>,
dest: Vec<String>,
},
}
#[allow(dead_code)]
pub struct Commander {
create_root: CreateRoot,
create_section: CreateSection,
create_item: CreateItem,
update_item: UpdateItem,
toggle_item: ToggleItem,
}
impl Commander {
pub fn new(
create_root: CreateRoot,
create_section: CreateSection,
create_item: CreateItem,
update_item: UpdateItem,
toggle_item: ToggleItem,
) -> Self {
Self {
create_root,
create_section,
create_item,
update_item,
toggle_item,
}
}
pub async fn execute(&self, cmd: Command) -> anyhow::Result<()> {
match cmd {
Command::CreateRoot { root } => {
self.create_root
.execute(create_root::Request { root })
.await?;
Ok(())
}
Command::CreateSection { root, path } => {
self.create_section
.execute(create_section::Request { root, path })
.await?;
Ok(())
}
Command::CreateItem {
root,
path,
title,
description,
state,
} => {
self.create_item
.execute(create_item::Request {
root,
path,
title,
description,
state,
})
.await?;
Ok(())
}
Command::UpdateItem {
root,
path,
title,
description,
state,
} => {
self.update_item
.execute(update_item::Request {
root,
path,
title,
description,
state,
})
.await?;
Ok(())
}
Command::ToggleItem { root, path } => {
self.toggle_item
.execute(toggle_item::Request { root, path })
.await?;
Ok(())
}
Command::Move { .. } => todo!(),
}
}
}
pub trait CommanderExt {
fn commander(&self) -> Commander;
}
impl CommanderExt for SharedState {
fn commander(&self) -> Commander {
Commander::new(
self.create_root_service(),
self.create_section_service(),
self.create_item_service(),
self.update_item_service(),
self.toggle_item_service(),
)
}
}

View File

@@ -0,0 +1,454 @@
use hyperlog_protos::hyperlog::{
graph_server::{Graph, GraphServer},
*,
};
use std::{collections::HashMap, net::SocketAddr};
use tonic::{transport, Response};
use crate::{
commands::{Command, Commander, CommanderExt},
querier::{Querier, QuerierExt},
state::SharedState,
};
#[allow(dead_code)]
pub struct Server {
querier: Querier,
commander: Commander,
}
impl Server {
pub fn new(querier: Querier, commander: Commander) -> Self {
Self { querier, commander }
}
}
#[tonic::async_trait]
impl Graph for Server {
async fn create_item(
&self,
request: tonic::Request<CreateItemRequest>,
) -> std::result::Result<tonic::Response<CreateItemResponse>, tonic::Status> {
let req = request.into_inner();
tracing::trace!("create item: req({:?})", req);
if req.root.is_empty() {
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"root cannot be empty".to_string(),
));
}
if req.path.is_empty() {
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot be empty".to_string(),
));
}
if req
.path
.iter()
.filter(|item| item.is_empty())
.collect::<Vec<_>>()
.first()
.is_some()
{
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot contain empty paths".to_string(),
));
}
if req
.path
.iter()
.filter(|item| item.contains("."))
.collect::<Vec<_>>()
.first()
.is_some()
{
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot contain `.`".to_string(),
));
}
let item = match req.item {
Some(i) => i,
None => {
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"item cannot contain empty or null".to_string(),
));
}
};
self.commander
.execute(Command::CreateItem {
root: req.root,
path: req.path,
title: item.title,
description: item.description,
state: match item.item_state {
Some(item_graph_item::ItemState::Done(_)) => {
hyperlog_core::log::ItemState::Done
}
Some(item_graph_item::ItemState::NotDone(_)) => {
hyperlog_core::log::ItemState::NotDone
}
None => hyperlog_core::log::ItemState::default(),
},
})
.await
.map_err(to_tonic_err)?;
Ok(Response::new(CreateItemResponse {}))
}
async fn create_root(
&self,
request: tonic::Request<CreateRootRequest>,
) -> std::result::Result<tonic::Response<CreateRootResponse>, tonic::Status> {
let req = request.into_inner();
tracing::trace!("create root: req({:?})", req);
if req.root.is_empty() {
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"root cannot be empty".to_string(),
));
}
self.commander
.execute(Command::CreateRoot { root: req.root })
.await
.map_err(to_tonic_err)?;
Ok(Response::new(CreateRootResponse {}))
}
async fn create_section(
&self,
request: tonic::Request<CreateSectionRequest>,
) -> std::result::Result<tonic::Response<CreateSectionResponse>, tonic::Status> {
let req = request.into_inner();
tracing::trace!("create section: req({:?})", req);
if req.root.is_empty() {
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"root cannot be empty".to_string(),
));
}
if req.path.is_empty() {
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot be empty".to_string(),
));
}
if req
.path
.iter()
.filter(|item| item.is_empty())
.collect::<Vec<_>>()
.first()
.is_some()
{
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot contain empty paths".to_string(),
));
}
if req
.path
.iter()
.filter(|item| item.contains("."))
.collect::<Vec<_>>()
.first()
.is_some()
{
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot contain `.`".to_string(),
));
}
self.commander
.execute(Command::CreateSection {
root: req.root,
path: req.path,
})
.await
.map_err(to_tonic_err)?;
Ok(Response::new(CreateSectionResponse {}))
}
async fn get(
&self,
request: tonic::Request<GetRequest>,
) -> std::result::Result<tonic::Response<GetReply>, tonic::Status> {
let msg = request.get_ref();
tracing::trace!("get: req({:?})", msg);
let res = self
.querier
.get(&msg.root, msg.paths.clone())
.await
.map_err(to_tonic_err)?;
match res {
Some(item) => Ok(Response::new(GetReply {
item: Some(to_native(&item).map_err(to_tonic_err)?),
})),
None => {
return Err(tonic::Status::new(
tonic::Code::NotFound,
"failed to find any valid roots",
))
}
}
}
async fn get_available_roots(
&self,
request: tonic::Request<GetAvailableRootsRequest>,
) -> std::result::Result<tonic::Response<GetAvailableRootsResponse>, tonic::Status> {
let req = request.into_inner();
tracing::trace!("get available roots: req({:?})", req);
let roots = match self
.querier
.get_available_roots()
.await
.map_err(to_tonic_err)?
{
Some(roots) => roots,
None => {
return Err(tonic::Status::new(
tonic::Code::NotFound,
"failed to find any valid roots",
))
}
};
Ok(Response::new(GetAvailableRootsResponse { roots }))
}
async fn update_item(
&self,
request: tonic::Request<UpdateItemRequest>,
) -> std::result::Result<tonic::Response<UpdateItemResponse>, tonic::Status> {
let req = request.into_inner();
tracing::trace!("update item: req({:?})", req);
if req.root.is_empty() {
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"root cannot be empty".to_string(),
));
}
if req.path.is_empty() {
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot be empty".to_string(),
));
}
if req
.path
.iter()
.filter(|item| item.is_empty())
.collect::<Vec<_>>()
.first()
.is_some()
{
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot contain empty paths".to_string(),
));
}
if req
.path
.iter()
.filter(|item| item.contains("."))
.collect::<Vec<_>>()
.first()
.is_some()
{
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot contain `.`".to_string(),
));
}
let item = match req.item {
Some(i) => i,
None => {
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"item cannot contain empty or null".to_string(),
));
}
};
self.commander
.execute(Command::UpdateItem {
root: req.root,
path: req.path,
title: item.title,
description: item.description,
state: match item.item_state {
Some(item_graph_item::ItemState::Done(_)) => {
hyperlog_core::log::ItemState::Done
}
Some(item_graph_item::ItemState::NotDone(_)) => {
hyperlog_core::log::ItemState::NotDone
}
None => hyperlog_core::log::ItemState::default(),
},
})
.await
.map_err(to_tonic_err)?;
Ok(Response::new(UpdateItemResponse {}))
}
async fn toggle_item(
&self,
request: tonic::Request<ToggleItemRequest>,
) -> std::result::Result<tonic::Response<ToggleItemResponse>, tonic::Status> {
let req = request.into_inner();
tracing::trace!("update item: req({:?})", req);
if req.root.is_empty() {
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"root cannot be empty".to_string(),
));
}
if req.path.is_empty() {
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot be empty".to_string(),
));
}
if req
.path
.iter()
.filter(|item| item.is_empty())
.collect::<Vec<_>>()
.first()
.is_some()
{
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot contain empty paths".to_string(),
));
}
if req
.path
.iter()
.filter(|item| item.contains("."))
.collect::<Vec<_>>()
.first()
.is_some()
{
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot contain `.`".to_string(),
));
}
self.commander
.execute(Command::ToggleItem {
root: req.root,
path: req.path,
})
.await
.map_err(to_tonic_err)?;
Ok(Response::new(ToggleItemResponse {}))
}
}
fn to_native(from: &hyperlog_core::log::GraphItem) -> anyhow::Result<GraphItem> {
match from {
hyperlog_core::log::GraphItem::User(section)
| hyperlog_core::log::GraphItem::Section(section) => {
let mut root = HashMap::new();
for (key, value) in section.iter() {
root.insert(key.to_string(), to_native(value)?);
}
match from {
hyperlog_core::log::GraphItem::User(_) => Ok(GraphItem {
contents: Some(graph_item::Contents::User(UserGraphItem { items: root })),
}),
hyperlog_core::log::GraphItem::Section(_) => Ok(GraphItem {
contents: Some(graph_item::Contents::Section(SectionGraphItem {
items: root,
})),
}),
_ => {
todo!()
}
}
}
hyperlog_core::log::GraphItem::Item {
title,
description,
state,
} => Ok(GraphItem {
contents: Some(graph_item::Contents::Item(ItemGraphItem {
title: title.to_owned(),
description: description.to_owned(),
item_state: Some(match state {
hyperlog_core::log::ItemState::NotDone => {
item_graph_item::ItemState::NotDone(ItemStateNotDone {})
}
hyperlog_core::log::ItemState::Done => {
item_graph_item::ItemState::Done(ItemStateDone {})
}
}),
})),
}),
}
}
// TODO: create more defined protobuf categories for errors
fn to_tonic_err(err: anyhow::Error) -> tonic::Status {
tonic::Status::new(tonic::Code::Unknown, err.to_string())
}
pub trait ServerExt {
fn grpc_server(&self) -> Server;
}
impl ServerExt for SharedState {
fn grpc_server(&self) -> Server {
Server::new(self.querier(), self.commander())
}
}
pub async fn serve(state: &SharedState, host: SocketAddr) -> anyhow::Result<()> {
tracing::info!("listening on {}", host);
let graph_server = state.grpc_server();
transport::Server::builder()
.add_service(GraphServer::new(graph_server))
.serve(host)
.await?;
Ok(())
}

View File

@@ -0,0 +1,40 @@
use std::net::SocketAddr;
use axum::{extract::MatchedPath, http::Request, routing::get, Router};
use tower_http::trace::TraceLayer;
use crate::state::SharedState;
async fn root() -> &'static str {
"Hello, hyperlog!"
}
pub async fn serve(state: &SharedState, host: &SocketAddr) -> anyhow::Result<()> {
let app = Router::new()
.route("/", get(root))
.with_state(state.clone())
.layer(
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
// Log the matched route's path (with placeholders not filled in).
// Use request.uri() or OriginalUri if you want the real path.
let matched_path = request
.extensions()
.get::<MatchedPath>()
.map(MatchedPath::as_str);
tracing::info_span!(
"http_request",
method = ?request.method(),
matched_path,
some_other_field = tracing::field::Empty,
)
}), // ...
);
tracing::info!("listening on {}", host);
let listener = tokio::net::TcpListener::bind(host).await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
Ok(())
}

View File

@@ -0,0 +1,40 @@
use std::net::SocketAddr;
use axum::{extract::MatchedPath, http::Request, routing::get, Router};
use tower_http::trace::TraceLayer;
use crate::state::SharedState;
async fn root() -> &'static str {
"Hello, hyperlog!"
}
pub async fn serve(state: &SharedState, host: &SocketAddr) -> anyhow::Result<()> {
let app = Router::new()
.route("/", get(root))
.with_state(state.clone())
.layer(
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
// Log the matched route's path (with placeholders not filled in).
// Use request.uri() or OriginalUri if you want the real path.
let matched_path = request
.extensions()
.get::<MatchedPath>()
.map(MatchedPath::as_str);
tracing::info_span!(
"http_request",
method = ?request.method(),
matched_path,
some_other_field = tracing::field::Empty,
)
}), // ...
);
tracing::info!("listening on {}", host);
let listener = tokio::net::TcpListener::bind(host).await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
Ok(())
}

View File

@@ -0,0 +1,50 @@
#![feature(map_try_insert)]
use std::{net::SocketAddr, sync::Arc};
use crate::state::{SharedState, State};
mod external_grpc;
mod external_http;
mod internal_http;
mod commands;
mod querier;
mod state;
mod services;
#[derive(Clone)]
pub struct ServeOptions {
pub external_http: SocketAddr,
pub internal_http: SocketAddr,
pub external_grpc: SocketAddr,
}
pub async fn serve(opts: ServeOptions) -> anyhow::Result<()> {
let ctrl_c = async {
tokio::signal::ctrl_c().await.unwrap();
tracing::info!("kill signal received, shutting down");
};
tracing::debug!("setting up dependencies");
let state = SharedState(Arc::new(State::new().await?));
tracing::debug!("serve starting");
tokio::select!(
res = external_http::serve(&state, &opts.external_http) => {
res?
},
res = internal_http::serve(&state, &opts.internal_http) => {
res?
},
res = external_grpc::serve(&state, opts.external_grpc) => {
res?
}
() = ctrl_c => {}
);
tracing::debug!("serve finalized");
Ok(())
}

View File

@@ -0,0 +1,62 @@
use hyperlog_core::log::GraphItem;
use crate::{
services::{
get_available_roots::{self, GetAvailableRoots, GetAvailableRootsExt},
get_graph::{GetGraph, GetGraphExt},
},
state::SharedState,
};
pub struct Querier {
get_available_roots: GetAvailableRoots,
get_graph: GetGraph,
}
impl Querier {
pub fn new(get_available_roots: GetAvailableRoots, get_graph: GetGraph) -> Self {
Self {
get_available_roots,
get_graph,
}
}
pub async fn get_available_roots(&self) -> anyhow::Result<Option<Vec<String>>> {
let res = self
.get_available_roots
.execute(get_available_roots::Request {})
.await?;
if res.roots.is_empty() {
return Ok(None);
}
Ok(Some(res.roots))
}
pub async fn get(
&self,
root: &str,
path: impl IntoIterator<Item = impl Into<String>>,
) -> anyhow::Result<Option<GraphItem>> {
let graph = self
.get_graph
.execute(crate::services::get_graph::Request {
root: root.into(),
path: path.into_iter().map(|s| s.into()).collect(),
})
.await?;
Ok(Some(graph.item))
}
}
pub trait QuerierExt {
fn querier(&self) -> Querier;
}
impl QuerierExt for SharedState {
fn querier(&self) -> Querier {
Querier::new(self.get_available_roots_service(), self.get_graph_service())
}
}

View File

@@ -0,0 +1,8 @@
pub mod create_item;
pub mod create_root;
pub mod create_section;
pub mod toggle_item;
pub mod update_item;
pub mod get_available_roots;
pub mod get_graph;

View File

@@ -0,0 +1,104 @@
use hyperlog_core::log::ItemState;
use sqlx::types::Json;
use crate::state::SharedState;
#[derive(Clone)]
pub struct CreateItem {
db: sqlx::PgPool,
}
pub struct Request {
pub root: String,
pub path: Vec<String>,
pub title: String,
pub description: String,
pub state: ItemState,
}
pub struct Response {}
#[derive(serde::Serialize)]
struct ItemContent {
pub title: String,
pub description: String,
pub state: ItemState,
}
#[derive(sqlx::FromRow)]
struct Root {
id: uuid::Uuid,
}
#[derive(sqlx::FromRow)]
struct Section {}
impl CreateItem {
pub fn new(db: sqlx::PgPool) -> Self {
Self { db }
}
pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {
let Root { id: root_id, .. } =
sqlx::query_as(r#"SELECT * FROM roots WHERE root_name = $1"#)
.bind(req.root)
.fetch_one(&self.db)
.await?;
match req.path.split_last() {
Some((_, section_path)) => {
if !section_path.is_empty() {
let Section { .. } = sqlx::query_as(
r#"
SELECT
*
FROM
nodes
WHERE
root_id = $1 AND
path = $2 AND
item_type = 'SECTION'
"#,
)
.bind(root_id)
.bind(section_path.join("."))
.fetch_one(&self.db)
.await?;
}
let node_id = uuid::Uuid::new_v4();
sqlx::query(
r#"
INSERT INTO nodes
(id, root_id, path, item_type, item_content)
VALUES
($1, $2, $3, $4, $5)"#,
)
.bind(node_id)
.bind(root_id)
.bind(req.path.join("."))
.bind("ITEM".to_string())
.bind(Json(ItemContent {
title: req.title,
description: req.description,
state: req.state,
}))
.execute(&self.db)
.await?;
}
None => anyhow::bail!("path most contain at least one item"),
}
Ok(Response {})
}
}
pub trait CreateItemExt {
fn create_item_service(&self) -> CreateItem;
}
impl CreateItemExt for SharedState {
fn create_item_service(&self) -> CreateItem {
CreateItem::new(self.db.clone())
}
}

View File

@@ -0,0 +1,38 @@
use crate::state::SharedState;
#[derive(Clone)]
pub struct CreateRoot {
db: sqlx::PgPool,
}
pub struct Request {
pub root: String,
}
pub struct Response {}
impl CreateRoot {
pub fn new(db: sqlx::PgPool) -> Self {
Self { db }
}
pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {
let root_id = uuid::Uuid::new_v4();
sqlx::query(r#"INSERT INTO roots (id, root_name) VALUES ($1, $2)"#)
.bind(root_id)
.bind(req.root)
.execute(&self.db)
.await?;
Ok(Response {})
}
}
pub trait CreateRootExt {
fn create_root_service(&self) -> CreateRoot;
}
impl CreateRootExt for SharedState {
fn create_root_service(&self) -> CreateRoot {
CreateRoot::new(self.db.clone())
}
}

View File

@@ -0,0 +1,61 @@
use crate::state::SharedState;
#[derive(Clone)]
pub struct CreateSection {
db: sqlx::PgPool,
}
pub struct Request {
pub root: String,
pub path: Vec<String>,
}
pub struct Response {}
#[derive(sqlx::FromRow)]
struct Root {
id: uuid::Uuid,
}
impl CreateSection {
pub fn new(db: sqlx::PgPool) -> Self {
Self { db }
}
pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {
let Root { id: root_id, .. } =
sqlx::query_as(r#"SELECT * FROM roots WHERE root_name = $1"#)
.bind(req.root)
.fetch_one(&self.db)
.await?;
// FIXME: implement consistency check on path
let node_id = uuid::Uuid::new_v4();
sqlx::query(
r#"
INSERT INTO nodes
(id, root_id, path, item_type, item_content)
VALUES
($1, $2, $3, $4, $5)"#,
)
.bind(node_id)
.bind(root_id)
.bind(req.path.join("."))
.bind("SECTION".to_string())
.bind(None::<serde_json::Value>)
.execute(&self.db)
.await?;
Ok(Response {})
}
}
pub trait CreateSectionExt {
fn create_section_service(&self) -> CreateSection;
}
impl CreateSectionExt for SharedState {
fn create_section_service(&self) -> CreateSection {
CreateSection::new(self.db.clone())
}
}

View File

@@ -0,0 +1,51 @@
use crate::state::SharedState;
#[derive(Clone)]
pub struct GetAvailableRoots {
db: sqlx::PgPool,
}
pub struct Request {}
pub struct Response {
pub roots: Vec<String>,
}
#[derive(sqlx::FromRow)]
pub struct Root {
root_name: String,
}
impl GetAvailableRoots {
pub fn new(db: sqlx::PgPool) -> Self {
Self { db }
}
pub async fn execute(&self, _req: Request) -> anyhow::Result<Response> {
let roots: Vec<Root> = sqlx::query_as(
r#"
SELECT
*
FROM
roots
LIMIT
100
"#,
)
.fetch_all(&self.db)
.await?;
Ok(Response {
roots: roots.into_iter().map(|i| i.root_name).collect(),
})
}
}
pub trait GetAvailableRootsExt {
fn get_available_roots_service(&self) -> GetAvailableRoots;
}
impl GetAvailableRootsExt for SharedState {
fn get_available_roots_service(&self) -> GetAvailableRoots {
GetAvailableRoots::new(self.db.clone())
}
}

View File

@@ -0,0 +1,368 @@
use std::collections::BTreeMap;
use hyperlog_core::log::{GraphItem, ItemState};
use serde::Deserialize;
use sqlx::types::Json;
use crate::state::SharedState;
use self::engine::Engine;
#[derive(Clone)]
pub struct GetGraph {
db: sqlx::PgPool,
}
pub struct Request {
pub root: String,
pub path: Vec<String>,
}
pub struct Response {
pub item: GraphItem,
}
#[derive(sqlx::FromRow)]
struct Root {
id: uuid::Uuid,
}
#[derive(Deserialize)]
struct Item {
title: String,
description: String,
state: ItemState,
}
#[derive(sqlx::FromRow, Debug)]
struct Node {
path: String,
item_type: String,
item_content: Option<Json<serde_json::Value>>,
}
impl GetGraph {
pub fn new(db: sqlx::PgPool) -> Self {
Self { db }
}
pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {
let Root { id: root_id, .. } =
sqlx::query_as(r#"SELECT * FROM roots WHERE root_name = $1"#)
.bind(&req.root)
.fetch_one(&self.db)
.await?;
let nodes: Vec<Node> = sqlx::query_as(
r#"
SELECT
*
FROM
nodes
WHERE
root_id = $1
LIMIT
1000
"#,
)
.bind(root_id)
.fetch_all(&self.db)
.await?;
let item = self.build_graph(req.root, req.path, nodes)?;
Ok(Response { item })
}
fn build_graph(
&self,
root: String,
path: Vec<String>,
mut nodes: Vec<Node>,
) -> anyhow::Result<GraphItem> {
nodes.sort_by(|a, b| a.path.cmp(&b.path));
let mut engine = Engine::default();
engine.create_root(&root)?;
self.get_graph_items(&root, &mut engine, &nodes)?;
engine
.get(&root, &path.iter().map(|s| s.as_str()).collect::<Vec<_>>())
.ok_or(anyhow::anyhow!("failed to find a valid graph"))
.cloned()
}
fn get_graph_items(
&self,
root: &str,
engine: &mut Engine,
nodes: &Vec<Node>,
) -> anyhow::Result<()> {
for node in nodes {
if let Some(item) = self.get_graph_item(node) {
let path = node.path.split('.').collect::<Vec<_>>();
engine.create(root, &path, item)?;
}
}
Ok(())
}
fn get_graph_item(&self, node: &Node) -> Option<GraphItem> {
match node.item_type.as_str() {
"SECTION" => Some(GraphItem::Section(BTreeMap::default())),
"ITEM" => {
if let Some(content) = &node.item_content {
let item: Item = serde_json::from_value(content.0.clone()).ok()?;
Some(GraphItem::Item {
title: item.title,
description: item.description,
state: item.state,
})
} else {
None
}
}
_ => None,
}
}
}
pub trait GetGraphExt {
fn get_graph_service(&self) -> GetGraph;
}
impl GetGraphExt for SharedState {
fn get_graph_service(&self) -> GetGraph {
GetGraph::new(self.db.clone())
}
}
mod engine {
use std::{collections::BTreeMap, fmt::Display};
use anyhow::{anyhow, Context};
use hyperlog_core::log::{Graph, GraphItem, ItemState};
#[derive(Default)]
pub struct Engine {
graph: Graph,
}
impl Engine {
#[allow(dead_code)]
pub fn engine_from_str(input: &str) -> anyhow::Result<Self> {
let graph: Graph = serde_json::from_str(input)?;
Ok(Self { graph })
}
#[allow(dead_code)]
pub fn to_str(&self) -> anyhow::Result<String> {
serde_json::to_string_pretty(&self.graph).context("failed to serialize graph")
}
pub fn create_root(&mut self, root: &str) -> anyhow::Result<()> {
self.graph
.try_insert(root.to_string(), GraphItem::User(BTreeMap::default()))
.map_err(|_| anyhow!("entry was already found, aborting"))?;
Ok(())
}
pub fn create(&mut self, root: &str, path: &[&str], item: GraphItem) -> anyhow::Result<()> {
let graph = &mut self.graph;
let (last, items) = path.split_last().ok_or(anyhow!(
"path cannot be empty, must contain at least one item"
))?;
let root = graph
.get_mut(root)
.ok_or(anyhow!("root was missing a user, aborting"))?;
let mut current_item = root;
for section in items {
match current_item {
GraphItem::User(u) => match u.get_mut(section.to_owned()) {
Some(graph_item) => {
current_item = graph_item;
}
None => anyhow::bail!("path: {} section was not found", section),
},
GraphItem::Item { .. } => anyhow::bail!("path: {} was already found", section),
GraphItem::Section(s) => match s.get_mut(section.to_owned()) {
Some(graph_item) => {
current_item = graph_item;
}
None => anyhow::bail!("path: {} section was not found", section),
},
}
}
match current_item {
GraphItem::User(u) => {
u.insert(last.to_string(), item);
}
GraphItem::Section(s) => {
s.insert(last.to_string(), item);
}
GraphItem::Item { .. } => anyhow::bail!("cannot insert an item into an item"),
}
Ok(())
}
pub fn get(&self, root: &str, path: &[&str]) -> Option<&GraphItem> {
let root = self.graph.get(root)?;
root.get(path)
}
#[allow(dead_code)]
pub fn get_mut(&mut self, root: &str, path: &[&str]) -> Option<&mut GraphItem> {
let root = self.graph.get_mut(root)?;
root.get_mut(path)
}
#[allow(dead_code)]
pub fn take(&mut self, root: &str, path: &[&str]) -> Option<GraphItem> {
let root = self.graph.get_mut(root)?;
root.take(path)
}
#[allow(dead_code)]
pub fn section_move(
&mut self,
root: &str,
src_path: &[&str],
dest_path: &[&str],
) -> anyhow::Result<()> {
let src = self
.take(root, src_path)
.ok_or(anyhow!("failed to find source path"))?;
let dest = self
.get_mut(root, dest_path)
.ok_or(anyhow!("failed to find destination"))?;
let src_item = src_path
.last()
.ok_or(anyhow!("src path must have at least one item"))?;
match dest {
GraphItem::User(u) => {
u.try_insert(src_item.to_string(), src)
.map_err(|_e| anyhow!("key was already found, aborting: {}", src_item))?;
}
GraphItem::Section(s) => {
s.try_insert(src_item.to_string(), src)
.map_err(|_e| anyhow!("key was already found, aborting: {}", src_item))?;
}
GraphItem::Item { .. } => {
anyhow::bail!(
"failed to insert src at item, item doesn't support arbitrary items"
)
}
}
Ok(())
}
#[allow(dead_code)]
pub fn delete(&mut self, root: &str, path: &[&str]) -> anyhow::Result<()> {
self.take(root, path)
.map(|_| ())
.ok_or(anyhow!("item was not found"))
}
#[allow(dead_code)]
pub fn toggle_item(&mut self, root: &str, path: &[&str]) -> anyhow::Result<()> {
if let Some(item) = self.get_mut(root, path) {
match item {
GraphItem::Item { state, .. } => match state {
ItemState::NotDone => *state = ItemState::Done,
ItemState::Done => *state = ItemState::NotDone,
},
_ => {
anyhow::bail!("{}.{:?} is not an item", root, path)
}
}
}
Ok(())
}
#[allow(dead_code)]
pub fn update_item(
&mut self,
root: &str,
path: &[&str],
item: &GraphItem,
) -> anyhow::Result<()> {
if let Some((name, dest_last)) = path.split_last() {
if let Some(parent) = self.get_mut(root, dest_last) {
match parent {
GraphItem::User(s) | GraphItem::Section(s) => {
if let Some(mut existing) = s.remove(*name) {
match (&mut existing, item) {
(
GraphItem::Item {
title: ex_title,
description: ex_desc,
state: ex_state,
},
GraphItem::Item {
title,
description,
state,
},
) => {
ex_title.clone_from(title);
ex_desc.clone_from(description);
ex_state.clone_from(state);
let title = title.replace(".", "-");
s.insert(title, existing.clone());
}
_ => {
anyhow::bail!(
"path: {}.{} found is not an item",
root,
path.join(".")
)
}
}
}
}
GraphItem::Item { .. } => {
anyhow::bail!("cannot rename when item is placed in an item")
}
}
}
}
Ok(())
}
#[allow(dead_code)]
pub fn get_roots(&self) -> Option<Vec<String>> {
let items = self.graph.keys().cloned().collect::<Vec<_>>();
if items.is_empty() {
None
} else {
Some(items)
}
}
}
impl Display for Engine {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let output = serde_json::to_string_pretty(&self.graph).unwrap();
f.write_str(&output)
}
}
}

View File

@@ -0,0 +1,105 @@
use hyperlog_core::log::ItemState;
use sqlx::types::Json;
use crate::state::SharedState;
#[derive(Clone)]
pub struct ToggleItem {
db: sqlx::PgPool,
}
pub struct Request {
pub root: String,
pub path: Vec<String>,
}
pub struct Response {}
#[derive(serde::Serialize, serde::Deserialize)]
struct ItemContent {
pub title: String,
pub description: String,
pub state: ItemState,
}
#[derive(sqlx::FromRow)]
struct Root {
id: uuid::Uuid,
}
#[derive(sqlx::FromRow)]
struct Node {
id: uuid::Uuid,
item_content: Option<Json<ItemContent>>,
}
impl ToggleItem {
pub fn new(db: sqlx::PgPool) -> Self {
Self { db }
}
pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {
let Root { id: root_id, .. } =
sqlx::query_as(r#"SELECT * FROM roots WHERE root_name = $1"#)
.bind(req.root)
.fetch_one(&self.db)
.await?;
let Node {
id: node_id,
mut item_content,
} = sqlx::query_as(
r#"
SELECT
*
FROM
nodes
WHERE
root_id = $1
AND path = $2
AND item_type = $3
"#,
)
.bind(root_id)
.bind(req.path.join("."))
.bind("ITEM")
.fetch_one(&self.db)
.await?;
if let Some(ref mut content) = item_content {
content.state = match content.state {
ItemState::NotDone => ItemState::Done,
ItemState::Done => ItemState::NotDone,
}
}
let res = sqlx::query(
r#"
UPDATE
nodes
SET
item_content = $1
WHERE
id = $2
"#,
)
.bind(item_content)
.bind(node_id)
.execute(&self.db)
.await?;
if res.rows_affected() != 1 {
anyhow::bail!("failed to update item");
}
Ok(Response {})
}
}
pub trait ToggleItemExt {
fn toggle_item_service(&self) -> ToggleItem;
}
impl ToggleItemExt for SharedState {
fn toggle_item_service(&self) -> ToggleItem {
ToggleItem::new(self.db.clone())
}
}

View File

@@ -0,0 +1,107 @@
use hyperlog_core::log::ItemState;
use sqlx::types::Json;
use crate::state::SharedState;
#[derive(Clone)]
pub struct UpdateItem {
db: sqlx::PgPool,
}
pub struct Request {
pub root: String,
pub path: Vec<String>,
pub title: String,
pub description: String,
pub state: ItemState,
}
pub struct Response {}
#[derive(serde::Serialize)]
struct ItemContent {
pub title: String,
pub description: String,
pub state: ItemState,
}
#[derive(sqlx::FromRow)]
struct Root {
id: uuid::Uuid,
}
impl UpdateItem {
pub fn new(db: sqlx::PgPool) -> Self {
Self { db }
}
pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {
let Root { id: root_id, .. } =
sqlx::query_as(r#"SELECT * FROM roots WHERE root_name = $1"#)
.bind(req.root)
.fetch_one(&self.db)
.await?;
let Root { id: node_id } = sqlx::query_as(
r#"
SELECT
id
FROM
nodes
WHERE
root_id = $1
AND path = $2
AND item_type = $3
"#,
)
.bind(root_id)
.bind(req.path.join("."))
.bind("ITEM")
.fetch_one(&self.db)
.await?;
let (_, rest) = req
.path
.split_last()
.ok_or(anyhow::anyhow!("expected path to have at least one item"))?;
let mut rest = rest.to_vec();
rest.push(req.title.replace(".", "-"));
let res = sqlx::query(
r#"
UPDATE
nodes
SET
item_content = $1,
path = $2
WHERE
id = $3
"#,
)
.bind(Json(ItemContent {
title: req.title,
description: req.description,
state: req.state,
}))
.bind(rest.join("."))
.bind(node_id)
.execute(&self.db)
.await?;
if res.rows_affected() != 1 {
anyhow::bail!("failed to update item");
}
Ok(Response {})
}
}
pub trait UpdateItemExt {
fn update_item_service(&self) -> UpdateItem;
}
impl UpdateItemExt for SharedState {
fn update_item_service(&self) -> UpdateItem {
UpdateItem::new(self.db.clone())
}
}

View File

@@ -0,0 +1,37 @@
use std::{ops::Deref, sync::Arc};
use anyhow::Context;
use sqlx::{Pool, Postgres};
#[derive(Clone)]
pub struct SharedState(pub Arc<State>);
impl Deref for SharedState {
type Target = Arc<State>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct State {
pub db: Pool<Postgres>,
}
impl State {
pub async fn new() -> anyhow::Result<Self> {
let db = sqlx::PgPool::connect(
&std::env::var("DATABASE_URL").context("DATABASE_URL is not set")?,
)
.await?;
sqlx::migrate!("migrations/crdb")
.set_locking(false)
.run(&db)
.await?;
let _ = sqlx::query("SELECT 1;").fetch_one(&db).await?;
Ok(Self { db })
}
}

View File

@@ -6,19 +6,26 @@ repository = "https://git.front.kjuulh.io/kjuulh/hyperlog"
[dependencies]
hyperlog-core.workspace = true
hyperlog-protos.workspace = true
anyhow.workspace = true
tokio.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
serde.workspace = true
serde_json.workspace = true
itertools.workspace = true
tonic.workspace = true
futures.workspace = true
ratatui = "0.26.2"
crossterm = { version = "0.27.0", features = ["event-stream"] }
directories = "5.0.1"
human-panic = "2.0.0"
ropey = "1.6.1"
bus = "2.4.1"
dirs = "5.0.1"
[dev-dependencies]
similar-asserts = "1.5.0"
tempfile = "3.10.1"

View File

@@ -5,8 +5,12 @@ use ratatui::{
};
use crate::{
command_parser::CommandParser, commands::IntoCommand,
components::graph_explorer::GraphExplorer, state::SharedState, Msg,
command_parser::CommandParser,
commands::{batch::BatchCommand, IntoCommand},
components::graph_explorer::GraphExplorer,
models::IOEvent,
state::SharedState,
Msg,
};
use self::{
@@ -26,10 +30,10 @@ pub enum Dialog {
}
impl Dialog {
pub fn get_command(&self) -> Option<hyperlog_core::commander::Command> {
pub fn get_command(&self) -> Option<impl IntoCommand> {
match self {
Dialog::CreateItem { state } => state.get_command(),
Dialog::EditItem { state } => state.get_command(),
Dialog::CreateItem { state } => state.get_command().map(|c| c.into_command()),
Dialog::EditItem { state } => state.get_command().map(|c| c.into_command()),
}
}
}
@@ -82,12 +86,21 @@ impl<'a> App<'a> {
pub fn update(&mut self, msg: Msg) -> anyhow::Result<impl IntoCommand> {
tracing::trace!("handling msg: {:?}", msg);
let mut batch = BatchCommand::default();
match &msg {
Msg::ItemCreated(IOEvent::Success(()))
| Msg::ItemUpdated(IOEvent::Success(()))
| Msg::SectionCreated(IOEvent::Success(()))
| Msg::ItemToggled(IOEvent::Success(())) => {
batch.with(self.graph_explorer.new_update_graph());
}
Msg::MoveRight => self.graph_explorer.move_right()?,
Msg::MoveLeft => self.graph_explorer.move_left()?,
Msg::MoveDown => self.graph_explorer.move_down()?,
Msg::MoveUp => self.graph_explorer.move_up()?,
Msg::OpenCreateItemDialog => self.open_dialog(),
Msg::OpenCreateItemDialogBelow => self.open_dialog_below(),
Msg::OpenEditItemDialog { item } => self.open_edit_item_dialog(item),
Msg::EnterInsertMode => self.mode = Mode::Insert,
Msg::EnterViewMode => self.mode = Mode::View,
@@ -97,7 +110,10 @@ impl<'a> App<'a> {
}
Msg::Interact => match self.focus {
AppFocus::Dialog => {}
AppFocus::Graph => self.graph_explorer.interact()?,
AppFocus::Graph => {
let cmd = self.graph_explorer.interact()?;
batch.with(cmd);
}
},
Msg::SubmitCommand { command } => {
tracing::info!("submitting command");
@@ -108,11 +124,9 @@ impl<'a> App<'a> {
if command.is_write() {
if let Some(dialog) = &self.dialog {
if let Some(output) = dialog.get_command() {
self.state.commander.execute(output)?;
batch.with(output.into_command());
}
}
self.graph_explorer.update_graph()?;
}
if command.is_quit() {
@@ -121,26 +135,31 @@ impl<'a> App<'a> {
}
}
AppFocus::Graph => {
if let Some(msg) = self.graph_explorer.execute_command(&command)? {
if let Some(cmd) = self.graph_explorer.execute_command(&command)? {
self.command = None;
return Ok(msg.into_command());
batch.with(cmd);
}
if command.is_quit() {
return Ok(Msg::QuitApp.into_command());
batch.with(Msg::QuitApp.into_command());
}
}
}
}
self.command = None;
return Ok(Msg::EnterViewMode.into_command());
batch.with(Msg::EnterViewMode.into_command());
}
_ => {}
}
let cmd = self.graph_explorer.inner.update(&msg);
if let Some(cmd) = cmd {
batch.with(cmd);
}
if let Some(command) = &mut self.command {
let cmd = command.update(&msg)?;
return Ok(cmd.into_command());
batch.with(cmd);
} else if let Some(dialog) = &mut self.dialog {
match dialog {
Dialog::CreateItem { state } => state.update(&msg)?,
@@ -148,7 +167,7 @@ impl<'a> App<'a> {
}
}
Ok(().into_command())
Ok(batch.into_command())
}
fn open_dialog(&mut self) {
@@ -158,18 +177,34 @@ impl<'a> App<'a> {
self.focus = AppFocus::Dialog;
self.dialog = Some(Dialog::CreateItem {
state: CreateItemState::new(root, path),
state: CreateItemState::new(&self.state, root, path),
});
}
}
fn open_dialog_below(&mut self) {
if self.dialog.is_none() {
let root = self.root.clone();
let path = self.graph_explorer.get_current_path();
if let Some((_, rest)) = path.split_last() {
let path = rest.to_vec();
self.focus = AppFocus::Dialog;
self.dialog = Some(Dialog::CreateItem {
state: CreateItemState::new(&self.state, root, path),
});
}
}
}
fn open_edit_item_dialog(&mut self, item: &GraphItem) {
if self.dialog.is_none() {
let root = self.root.clone();
let path = self.graph_explorer.get_current_path();
self.dialog = Some(Dialog::EditItem {
state: EditItemState::new(root, path, item),
state: EditItemState::new(&self.state, root, path, item),
});
self.command = None;
self.focus = AppFocus::Dialog;

View File

@@ -1,7 +1,12 @@
use hyperlog_core::log::ItemState;
use itertools::Itertools;
use ratatui::{prelude::*, widgets::*};
use crate::models::Msg;
use crate::{
commands::{create_item::CreateItemCommandExt, IntoCommand},
models::Msg,
state::SharedState,
};
use super::{InputBuffer, InputField};
@@ -23,10 +28,16 @@ pub struct CreateItemState {
description: InputBuffer,
focused: CreateItemFocused,
state: SharedState,
}
impl CreateItemState {
pub fn new(root: impl Into<String>, path: impl IntoIterator<Item = impl Into<String>>) -> Self {
pub fn new(
state: &SharedState,
root: impl Into<String>,
path: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
let root = root.into();
let path = path.into_iter().map(|p| p.into()).collect_vec();
@@ -37,6 +48,8 @@ impl CreateItemState {
title: Default::default(),
description: Default::default(),
focused: Default::default(),
state: state.clone(),
}
}
@@ -61,21 +74,29 @@ impl CreateItemState {
Ok(())
}
pub fn get_command(&self) -> Option<hyperlog_core::commander::Command> {
pub fn get_command(&self) -> Option<impl IntoCommand> {
let title = self.title.string();
let description = self.description.string();
if !title.is_empty() {
let mut path = self.path.clone();
path.push(title.replace([' ', '.'], "-"));
path.push(title.replace(['.'], "-"));
Some(hyperlog_core::commander::Command::CreateItem {
root: self.root.clone(),
path,
title: title.trim().into(),
description: description.trim().into(),
state: hyperlog_core::log::ItemState::NotDone,
})
Some(self.state.create_item_command().command(
&self.root,
&path.iter().map(|i| i.as_str()).collect_vec(),
title.trim(),
description.trim(),
&ItemState::NotDone,
))
// Some(commander::Command::CreateItem {
// root: self.root.clone(),
// path,
// title: title.trim().into(),
// description: description.trim().into(),
// state: hyperlog_core::log::ItemState::NotDone,
// })
} else {
None
}

View File

@@ -2,7 +2,11 @@ use hyperlog_core::log::GraphItem;
use itertools::Itertools;
use ratatui::{prelude::*, widgets::*};
use crate::models::Msg;
use crate::{
commands::{update_item::UpdateItemCommandExt, IntoCommand},
models::Msg,
state::SharedState,
};
use super::{InputBuffer, InputField};
@@ -26,10 +30,13 @@ pub struct EditItemState {
item: GraphItem,
focused: EditItemFocused,
state: SharedState,
}
impl EditItemState {
pub fn new(
state: &SharedState,
root: impl Into<String>,
path: impl IntoIterator<Item = impl Into<String>>,
item: &GraphItem,
@@ -47,6 +54,8 @@ impl EditItemState {
title.set_position(title_len);
Self {
state: state.clone(),
root,
path,
@@ -82,24 +91,36 @@ impl EditItemState {
Ok(())
}
pub fn get_command(&self) -> Option<hyperlog_core::commander::Command> {
pub fn get_command(&self) -> Option<impl IntoCommand> {
let title = self.title.string();
let description = self.description.string();
if !title.is_empty() {
let path = self.path.clone();
Some(hyperlog_core::commander::Command::UpdateItem {
root: self.root.clone(),
path,
title: title.trim().into(),
description: description.trim().into(),
state: match &self.item {
Some(self.state.update_item_command().command(
&self.root,
&path.iter().map(|s| s.as_str()).collect_vec(),
title.trim(),
description.trim(),
match &self.item {
GraphItem::User(_) => Default::default(),
GraphItem::Section(_) => Default::default(),
GraphItem::Item { state, .. } => state.clone(),
},
})
))
// Some(commander::Command::UpdateItem {
// root: self.root.clone(),
// path,
// title: title.trim().into(),
// description: description.trim().into(),
// state: match &self.item {
// GraphItem::User(_) => Default::default(),
// GraphItem::Section(_) => Default::default(),
// GraphItem::Item { state, .. } => state.clone(),
// },
// })
} else {
None
}

View File

@@ -6,10 +6,13 @@ pub enum Commands {
WriteQuit,
Archive,
CreateSection { name: String },
CreateItem { name: String },
CreateBelow { name: String },
Edit,
ShowAll,
HideDone,
Test,
}
impl Commands {
@@ -39,9 +42,16 @@ impl CommandParser {
"cs" | "create-section" => rest.first().map(|name| Commands::CreateSection {
name: name.to_string(),
}),
"ci" | "create-item" => Some(Commands::CreateItem {
name: rest.join(" ").to_string(),
}),
"cb" | "create-below" => Some(Commands::CreateBelow {
name: rest.join(" ").to_string(),
}),
"e" | "edit" => Some(Commands::Edit),
"show-all" => Some(Commands::ShowAll),
"hide-done" => Some(Commands::HideDone),
"test" => Some(Commands::Test),
_ => None,
},
None => None,

View File

@@ -0,0 +1,74 @@
use hyperlog_core::log::ItemState;
use serde::Serialize;
use tonic::transport::Channel;
use crate::{events::Events, shared_engine::SharedEngine, storage::Storage};
mod local;
mod remote;
#[derive(Serialize, PartialEq, Eq, Debug, Clone)]
pub enum Command {
CreateRoot {
root: String,
},
CreateSection {
root: String,
path: Vec<String>,
},
CreateItem {
root: String,
path: Vec<String>,
title: String,
description: String,
state: ItemState,
},
UpdateItem {
root: String,
path: Vec<String>,
title: String,
description: String,
state: ItemState,
},
ToggleItem {
root: String,
path: Vec<String>,
},
Move {
root: String,
src: Vec<String>,
dest: Vec<String>,
},
}
#[derive(Clone)]
enum CommanderVariant {
Local(local::Commander),
Remote(remote::Commander),
}
#[derive(Clone)]
pub struct Commander {
variant: CommanderVariant,
}
impl Commander {
pub fn local(engine: SharedEngine, storage: Storage, events: Events) -> anyhow::Result<Self> {
Ok(Self {
variant: CommanderVariant::Local(local::Commander::new(engine, storage, events)?),
})
}
pub fn remote(channel: Channel) -> anyhow::Result<Self> {
Ok(Self {
variant: CommanderVariant::Remote(remote::Commander::new(channel)?),
})
}
pub async fn execute(&self, cmd: Command) -> anyhow::Result<()> {
match &self.variant {
CommanderVariant::Local(commander) => commander.execute(cmd),
CommanderVariant::Remote(commander) => commander.execute(cmd).await,
}
}
}

View File

@@ -1,48 +1,12 @@
use std::collections::BTreeMap;
use serde::Serialize;
use hyperlog_core::log::GraphItem;
use crate::{
events::Events,
log::{GraphItem, ItemState},
shared_engine::SharedEngine,
storage::Storage,
};
use crate::{events::Events, shared_engine::SharedEngine, storage::Storage};
#[derive(Serialize, PartialEq, Eq, Debug, Clone)]
pub enum Command {
CreateRoot {
root: String,
},
CreateSection {
root: String,
path: Vec<String>,
},
CreateItem {
root: String,
path: Vec<String>,
title: String,
description: String,
state: ItemState,
},
UpdateItem {
root: String,
path: Vec<String>,
title: String,
description: String,
state: ItemState,
},
ToggleItem {
root: String,
path: Vec<String>,
},
Move {
root: String,
src: Vec<String>,
dest: Vec<String>,
},
}
use super::Command;
#[derive(Clone)]
pub struct Commander {
engine: SharedEngine,
storage: Storage,

View File

@@ -0,0 +1,149 @@
use hyperlog_protos::hyperlog::{graph_client::GraphClient, *};
use tonic::transport::Channel;
use super::Command;
#[allow(dead_code, unused_variables)]
#[derive(Clone)]
pub struct Commander {
channel: Channel,
}
#[allow(dead_code, unused_variables)]
impl Commander {
pub fn new(channel: Channel) -> anyhow::Result<Self> {
Ok(Self { channel })
}
pub async fn execute(&self, cmd: Command) -> anyhow::Result<()> {
tracing::debug!("executing event: {}", serde_json::to_string(&cmd)?);
match cmd.clone() {
Command::CreateRoot { root } => {
let channel = self.channel.clone();
let mut client = GraphClient::new(channel);
let request = tonic::Request::new(CreateRootRequest { root });
let response = client.create_root(request).await?;
let res = response.into_inner();
//self.engine.create_root(&root)?;
}
Command::CreateSection { root, path } => {
let channel = self.channel.clone();
let mut client = GraphClient::new(channel);
let request = tonic::Request::new(CreateSectionRequest { root, path });
let response = client.create_section(request).await?;
let res = response.into_inner();
// self.engine.create(
// &root,
// &path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
// GraphItem::Section(BTreeMap::default()),
// )?;
}
Command::CreateItem {
root,
path,
title,
description,
state,
} => {
let channel = self.channel.clone();
let mut client = GraphClient::new(channel);
let request = tonic::Request::new(CreateItemRequest {
root,
path,
item: Some(ItemGraphItem {
title,
description,
item_state: Some(match state {
hyperlog_core::log::ItemState::NotDone => {
item_graph_item::ItemState::NotDone(ItemStateNotDone {})
}
hyperlog_core::log::ItemState::Done => {
item_graph_item::ItemState::Done(ItemStateDone {})
}
}),
}),
});
let response = client.create_item(request).await?;
let res = response.into_inner();
// self.engine.create(
// &root,
// &path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
// GraphItem::Item {
// title,
// description,
// state,
// },
// )?
}
Command::Move { root, src, dest } => {
todo!()
// self.engine.section_move(
// &root,
// &src.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
// &dest.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
// )?
}
Command::ToggleItem { root, path } => {
let channel = self.channel.clone();
let mut client = GraphClient::new(channel);
let request = tonic::Request::new(ToggleItemRequest { root, path });
let response = client.toggle_item(request).await?;
let res = response.into_inner();
}
Command::UpdateItem {
root,
path,
title,
description,
state,
} => {
let channel = self.channel.clone();
let mut client = GraphClient::new(channel);
let request = tonic::Request::new(UpdateItemRequest {
root,
path,
item: Some(ItemGraphItem {
title,
description,
item_state: Some(match state {
hyperlog_core::log::ItemState::NotDone => {
item_graph_item::ItemState::NotDone(ItemStateNotDone {})
}
hyperlog_core::log::ItemState::Done => {
item_graph_item::ItemState::Done(ItemStateDone {})
}
}),
}),
});
let response = client.update_item(request).await?;
let res = response.into_inner();
// self.engine.update_item(
// &root,
// &path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
// GraphItem::Item {
// title,
// description,
// state,
// },
// )?
}
}
// self.storage.store(&self.engine)?;
// self.events.enque_command(cmd)?;
Ok(())
}
}

View File

@@ -1,3 +1,14 @@
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
pub mod batch;
pub mod create_item;
pub mod create_section;
pub mod open_update_item_dialog;
pub mod toggle_item;
pub mod update_graph;
pub mod update_item;
use crate::models::Msg;
pub trait IntoCommand {
@@ -6,7 +17,7 @@ pub trait IntoCommand {
impl IntoCommand for () {
fn into_command(self) -> Command {
Command::new(|| None)
Command::new(|_| None)
}
}
@@ -16,16 +27,47 @@ impl IntoCommand for Command {
}
}
type CommandFunc = dyn FnOnce(Dispatch) -> Option<Msg>;
pub struct Command {
func: Box<dyn FnOnce() -> Option<Msg>>,
func: Box<CommandFunc>,
}
impl Command {
pub fn new<T: FnOnce() -> Option<Msg> + 'static>(f: T) -> Self {
pub fn new<T: FnOnce(Dispatch) -> Option<Msg> + 'static>(f: T) -> Self {
Self { func: Box::new(f) }
}
pub fn execute(self) -> Option<Msg> {
self.func.call_once(())
pub fn execute(self, dispatch: Dispatch) -> Option<Msg> {
self.func.call_once((dispatch,))
}
}
pub fn create_dispatch() -> (Dispatch, Receiver) {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
(Dispatch { sender: tx }, Receiver { receiver: rx })
}
#[derive(Clone)]
pub struct Dispatch {
sender: UnboundedSender<Msg>,
}
impl Dispatch {
pub fn send(&self, msg: Msg) {
if let Err(e) = self.sender.send(msg) {
tracing::warn!("failed to send event: {}", e);
}
}
}
pub struct Receiver {
receiver: UnboundedReceiver<Msg>,
}
impl Receiver {
pub async fn next(&mut self) -> Option<Msg> {
self.receiver.recv().await
}
}

View File

@@ -0,0 +1,41 @@
use super::IntoCommand;
#[derive(Default)]
pub struct BatchCommand {
commands: Vec<super::Command>,
}
impl BatchCommand {
pub fn with(&mut self, cmd: impl IntoCommand) -> &mut Self {
self.commands.push(cmd.into_command());
self
}
}
impl IntoCommand for Vec<super::Command> {
fn into_command(self) -> super::Command {
BatchCommand::from(self).into_command()
}
}
impl From<Vec<super::Command>> for BatchCommand {
fn from(value: Vec<super::Command>) -> Self {
BatchCommand { commands: value }
}
}
impl IntoCommand for BatchCommand {
fn into_command(self) -> super::Command {
super::Command::new(|dispatch| {
for command in self.commands {
let msg = command.execute(dispatch.clone());
if let Some(msg) = msg {
dispatch.send(msg);
}
}
None
})
}
}

View File

@@ -0,0 +1,73 @@
use hyperlog_core::log::ItemState;
use itertools::Itertools;
use crate::{
commander::{self, Commander},
models::{IOEvent, Msg},
state::SharedState,
};
pub struct CreateItemCommand {
commander: Commander,
}
impl CreateItemCommand {
pub fn new(commander: Commander) -> Self {
Self { commander }
}
pub fn command(
self,
root: &str,
path: &[&str],
title: &str,
description: &str,
state: &ItemState,
) -> super::Command {
let root = root.to_owned();
let path = path.iter().map(|s| s.to_string()).collect_vec();
let title = title.to_string();
let description = description.to_string();
let state = state.clone();
super::Command::new(|dispatch| {
tokio::spawn(async move {
dispatch.send(Msg::ItemCreated(IOEvent::Initialized));
match self
.commander
.execute(commander::Command::CreateItem {
root,
path,
title,
description,
state,
})
.await
{
Ok(()) => {
#[cfg(debug_assertions)]
{
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
dispatch.send(Msg::ItemCreated(IOEvent::Success(())));
}
Err(e) => {
dispatch.send(Msg::ItemCreated(IOEvent::Failure(e.to_string())));
}
}
});
None
})
}
}
pub trait CreateItemCommandExt {
fn create_item_command(&self) -> CreateItemCommand;
}
impl CreateItemCommandExt for SharedState {
fn create_item_command(&self) -> CreateItemCommand {
CreateItemCommand::new(self.commander.clone())
}
}

View File

@@ -0,0 +1,59 @@
use itertools::Itertools;
use crate::{
commander::{self, Commander},
models::IOEvent,
state::SharedState,
};
pub struct CreateSectionCommand {
commander: Commander,
}
impl CreateSectionCommand {
pub fn new(commander: Commander) -> Self {
Self { commander }
}
pub fn command(self, root: &str, path: &[&str]) -> super::Command {
let root = root.to_owned();
let path = path.iter().map(|s| s.to_string()).collect_vec();
super::Command::new(|dispatch| {
tokio::spawn(async move {
dispatch.send(crate::models::Msg::SectionCreated(IOEvent::Initialized));
match self
.commander
.execute(commander::Command::CreateSection { root, path })
.await
{
Ok(()) => {
#[cfg(debug_assertions)]
{
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
dispatch.send(crate::models::Msg::SectionCreated(IOEvent::Success(())));
}
Err(e) => {
dispatch.send(crate::models::Msg::SectionCreated(IOEvent::Failure(
e.to_string(),
)));
}
}
});
None
})
}
}
pub trait CreateSectionCommandExt {
fn create_section_command(&self) -> CreateSectionCommand;
}
impl CreateSectionCommandExt for SharedState {
fn create_section_command(&self) -> CreateSectionCommand {
CreateSectionCommand::new(self.commander.clone())
}
}

View File

@@ -0,0 +1,59 @@
use crate::{
models::{IOEvent, Msg},
querier::Querier,
state::SharedState,
};
pub struct OpenUpdateItemDialogCommand {
querier: Querier,
}
impl OpenUpdateItemDialogCommand {
pub fn new(querier: Querier) -> Self {
Self { querier }
}
pub fn command(self, root: &str, path: Vec<String>) -> super::Command {
let root = root.to_string();
super::Command::new(|dispatch| {
tokio::spawn(async move {
dispatch.send(Msg::OpenUpdateItemDialog(IOEvent::Initialized));
let item = match self.querier.get_async(&root, path).await {
Ok(item) => match item {
Some(item) => {
dispatch.send(Msg::OpenUpdateItemDialog(IOEvent::Success(())));
item
}
None => {
dispatch.send(Msg::OpenUpdateItemDialog(IOEvent::Failure(
"failed to find a valid item for path".into(),
)));
return;
}
},
Err(e) => {
dispatch.send(Msg::OpenUpdateItemDialog(IOEvent::Failure(e.to_string())));
return;
}
};
dispatch.send(Msg::OpenEditItemDialog { item });
});
None
})
}
}
pub trait OpenUpdateItemDialogCommandExt {
fn open_update_item_dialog_command(&self) -> OpenUpdateItemDialogCommand;
}
impl OpenUpdateItemDialogCommandExt for SharedState {
fn open_update_item_dialog_command(&self) -> OpenUpdateItemDialogCommand {
OpenUpdateItemDialogCommand::new(self.querier.clone())
}
}

View File

@@ -0,0 +1,59 @@
use itertools::Itertools;
use crate::{
commander::{self, Commander},
models::IOEvent,
state::SharedState,
};
pub struct ToggleItemCommand {
commander: Commander,
}
impl ToggleItemCommand {
pub fn new(commander: Commander) -> Self {
Self { commander }
}
pub fn command(self, root: &str, path: &[&str]) -> super::Command {
let root = root.to_owned();
let path = path.iter().map(|s| s.to_string()).collect_vec();
super::Command::new(|dispatch| {
tokio::spawn(async move {
dispatch.send(crate::models::Msg::ItemToggled(IOEvent::Initialized));
match self
.commander
.execute(commander::Command::ToggleItem { root, path })
.await
{
Ok(()) => {
#[cfg(debug_assertions)]
{
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
dispatch.send(crate::models::Msg::ItemToggled(IOEvent::Success(())));
}
Err(e) => {
dispatch.send(crate::models::Msg::ItemToggled(IOEvent::Failure(
e.to_string(),
)));
}
}
});
None
})
}
}
pub trait ToggleItemCommandExt {
fn toggle_item_command(&self) -> ToggleItemCommand;
}
impl ToggleItemCommandExt for SharedState {
fn toggle_item_command(&self) -> ToggleItemCommand {
ToggleItemCommand::new(self.commander.clone())
}
}

View File

@@ -0,0 +1,61 @@
use itertools::Itertools;
use crate::{
models::{IOEvent, Msg},
querier::Querier,
state::SharedState,
};
pub struct UpdateGraphCommand {
querier: Querier,
}
impl UpdateGraphCommand {
pub fn new(querier: Querier) -> Self {
Self { querier }
}
pub fn command(self, root: &str, path: &[&str]) -> super::Command {
let root = root.to_owned();
let path = path.iter().map(|i| i.to_string()).collect_vec();
super::Command::new(|dispatch| {
tokio::spawn(async move {
let now = std::time::SystemTime::now();
dispatch.send(Msg::GraphUpdated(IOEvent::Initialized));
match self.querier.get_async(&root, path).await {
Ok(Some(graph)) => {
dispatch.send(Msg::GraphUpdated(IOEvent::Optimistic(graph.clone())));
#[cfg(debug_assertions)]
{
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
dispatch.send(Msg::GraphUpdated(IOEvent::Success(graph)))
}
Ok(None) => dispatch.send(Msg::GraphUpdated(IOEvent::Failure(
"graph was not found user root".into(),
))),
Err(e) => dispatch.send(Msg::GraphUpdated(IOEvent::Failure(format!("{e}")))),
}
let elapsed = now.elapsed().expect("to be able to get time");
tracing::trace!("UpdateGraphCommand took: {}nanos", elapsed.as_nanos());
});
None
})
}
}
pub trait UpdateGraphCommandExt {
fn update_graph_command(&self) -> UpdateGraphCommand;
}
impl UpdateGraphCommandExt for SharedState {
fn update_graph_command(&self) -> UpdateGraphCommand {
UpdateGraphCommand::new(self.querier.clone())
}
}

View File

@@ -0,0 +1,76 @@
use hyperlog_core::log::ItemState;
use itertools::Itertools;
use crate::{
commander::{self, Commander},
models::IOEvent,
state::SharedState,
};
pub struct UpdateItemCommand {
commander: Commander,
}
impl UpdateItemCommand {
pub fn new(commander: Commander) -> Self {
Self { commander }
}
pub fn command(
self,
root: &str,
path: &[&str],
title: &str,
description: &str,
state: ItemState,
) -> super::Command {
let root = root.to_owned();
let path = path.iter().map(|s| s.to_string()).collect_vec();
let title = title.to_string();
let description = description.to_string();
let state = state.clone();
super::Command::new(|dispatch| {
tokio::spawn(async move {
dispatch.send(crate::models::Msg::ItemUpdated(IOEvent::Initialized));
match self
.commander
.execute(commander::Command::UpdateItem {
root,
path,
title,
description,
state,
})
.await
{
Ok(()) => {
#[cfg(debug_assertions)]
{
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
dispatch.send(crate::models::Msg::ItemUpdated(IOEvent::Success(())));
}
Err(e) => {
dispatch.send(crate::models::Msg::ItemUpdated(IOEvent::Failure(
e.to_string(),
)));
}
}
});
None
})
}
}
pub trait UpdateItemCommandExt {
fn update_item_command(&self) -> UpdateItemCommand;
}
impl UpdateItemCommandExt for SharedState {
fn update_item_command(&self) -> UpdateItemCommand {
UpdateItemCommand::new(self.commander.clone())
}
}

View File

@@ -1,9 +1,18 @@
use anyhow::Result;
use hyperlog_core::log::GraphItem;
use itertools::Itertools;
use ratatui::{prelude::*, widgets::*};
use crate::{
command_parser::Commands, components::movement_graph::GraphItemType, models::Msg,
command_parser::Commands,
commands::{
batch::BatchCommand, create_item::CreateItemCommandExt,
create_section::CreateSectionCommandExt,
open_update_item_dialog::OpenUpdateItemDialogCommandExt, toggle_item::ToggleItemCommandExt,
update_graph::UpdateGraphCommandExt, Command, IntoCommand,
},
components::movement_graph::GraphItemType,
models::{IOEvent, Msg},
state::SharedState,
};
@@ -46,6 +55,31 @@ pub struct GraphExplorerState<'a> {
graph: Option<GraphItem>,
}
impl<'a> GraphExplorerState<'a> {
pub fn update(&mut self, msg: &Msg) -> Option<Command> {
if let Msg::GraphUpdated(graph_update) = msg {
match graph_update {
IOEvent::Initialized => {
tracing::trace!("initialized graph");
}
IOEvent::Success(graph) => {
tracing::trace!("graph updated successfully");
self.graph = Some(graph.clone());
}
IOEvent::Failure(e) => {
tracing::error!("graph update failed: {}", e);
}
IOEvent::Optimistic(graph) => {
tracing::trace!("graph updated optimistically");
self.graph = Some(graph.clone());
}
}
}
None
}
}
impl<'a> GraphExplorer<'a> {
pub fn new(root: String, state: SharedState) -> Self {
Self {
@@ -60,19 +94,31 @@ impl<'a> GraphExplorer<'a> {
}
}
pub fn update_graph(&mut self) -> Result<&mut Self> {
pub fn new_update_graph(&self) -> Command {
self.state.update_graph_command().command(
&self.inner.root,
&self
.inner
.current_path
.map(|p| p.split(".").collect_vec())
.unwrap_or_default(),
)
}
pub async fn update_graph(&mut self) -> Result<&mut Self> {
let now = std::time::SystemTime::now();
let graph = self
.state
.querier
.get(
.get_async(
&self.inner.root,
self.inner
.current_path
.map(|p| p.split('.').collect::<Vec<_>>())
.unwrap_or_default(),
)
.await?
.ok_or(anyhow::anyhow!("graph should've had an item"))?;
self.inner.graph = Some(graph);
@@ -184,7 +230,9 @@ impl<'a> GraphExplorer<'a> {
}
}
pub fn execute_command(&mut self, command: &Commands) -> anyhow::Result<Option<Msg>> {
pub fn execute_command(&mut self, command: &Commands) -> anyhow::Result<Option<Command>> {
let mut batch = BatchCommand::default();
match command {
Commands::Archive => {
if !self.get_current_path().is_empty() {
@@ -194,14 +242,55 @@ impl<'a> GraphExplorer<'a> {
Commands::CreateSection { name } => {
if !name.is_empty() {
let mut path = self.get_current_path();
path.push(name.replace(" ", "-").replace(".", "-"));
path.push(name.replace(".", "-"));
self.state.commander.execute(
hyperlog_core::commander::Command::CreateSection {
root: self.inner.root.clone(),
path,
},
)?;
// self.state
// .commander
// .execute(commander::Command::CreateSection {
// root: self.inner.root.clone(),
// path,
// })?;
let cmd = self.state.create_section_command().command(
&self.inner.root,
&path.iter().map(|i| i.as_str()).collect_vec(),
);
batch.with(cmd.into_command());
}
}
Commands::CreateItem { name } => {
if !name.is_empty() {
let mut path = self.get_current_path();
path.push(name.replace(".", " "));
let cmd = self.state.create_item_command().command(
&self.inner.root,
&path.iter().map(|i| i.as_str()).collect_vec(),
name,
"",
&hyperlog_core::log::ItemState::default(),
);
batch.with(cmd.into_command());
}
}
Commands::CreateBelow { name } => {
if !name.is_empty() {
let path = self.get_current_path();
if let Some((_, path)) = path.split_last() {
let mut path = path.to_vec();
path.push(name.replace(".", " "));
let cmd = self.state.create_item_command().command(
&self.inner.root,
&path.iter().map(|i| i.as_str()).collect_vec(),
name,
"",
&hyperlog_core::log::ItemState::default(),
);
batch.with(cmd.into_command());
}
}
}
Commands::Edit => {
@@ -218,11 +307,11 @@ impl<'a> GraphExplorer<'a> {
todo!("cannot edit section at the moment")
}
GraphItemType::Item { .. } => {
if let Some(item) = self.state.querier.get(&self.inner.root, path) {
if let GraphItem::Item { .. } = item {
return Ok(Some(Msg::OpenEditItemDialog { item }));
}
}
batch.with(
self.state
.open_update_item_dialog_command()
.command(&self.inner.root, path),
);
}
}
}
@@ -233,29 +322,54 @@ impl<'a> GraphExplorer<'a> {
Commands::HideDone => {
self.inner.display_options.filter_by = FilterBy::NotDone;
}
Commands::Test => {
return Ok(Some(Command::new(|dispatch| {
tokio::spawn(async move {
dispatch.send(Msg::MoveDown);
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
dispatch.send(Msg::EnterViewMode);
});
None
})));
}
_ => (),
}
self.update_graph()?;
//self.update_graph()?;
Ok(None)
Ok(Some(batch.into_command()))
}
pub(crate) fn interact(&mut self) -> anyhow::Result<()> {
pub(crate) fn interact(&mut self) -> anyhow::Result<Command> {
let mut batch = BatchCommand::default();
if !self.get_current_path().is_empty() {
tracing::info!("toggling state of items");
self.state
.commander
.execute(hyperlog_core::commander::Command::ToggleItem {
root: self.inner.root.to_string(),
path: self.get_current_path(),
})?;
// self.state
// .commander
// .execute(commander::Command::ToggleItem {
// root: self.inner.root.to_string(),
// path: self.get_current_path(),
// })?;
let cmd = self.state.toggle_item_command().command(
&self.inner.root,
&self
.get_current_path()
.iter()
.map(|i| i.as_str())
.collect_vec(),
);
batch.with(cmd.into_command());
}
self.update_graph()?;
//self.update_graph()?;
Ok(())
Ok(batch.into_command())
}
}

View File

@@ -0,0 +1,56 @@
use tonic::transport::Channel;
use crate::{
commander::Commander, events::Events, querier::Querier, shared_engine::SharedEngine,
storage::Storage,
};
#[allow(dead_code)]
pub struct State {
engine: SharedEngine,
pub storage: Storage,
events: Events,
pub commander: Commander,
pub querier: Querier,
}
pub enum Backend {
Local,
Remote,
}
impl State {
pub async fn new(backend: Backend) -> anyhow::Result<Self> {
let storage = Storage::new();
let engine = storage.load()?;
let events = Events::default();
let engine = SharedEngine::from(engine);
let (querier, commander) = match backend {
Backend::Local => (
Querier::local(&engine),
Commander::local(engine.clone(), storage.clone(), events.clone())?,
),
Backend::Remote => {
let channel = Channel::from_static("http://localhost:4000")
.connect()
.await?;
(
Querier::remote(channel.clone()).await?,
Commander::remote(channel)?,
)
}
};
Ok(Self {
engine: engine.clone(),
storage: storage.clone(),
events: events.clone(),
commander,
querier,
})
}
}

View File

@@ -1,8 +1,7 @@
use std::{collections::BTreeMap, fmt::Display};
use anyhow::{anyhow, Context};
use crate::log::{Graph, GraphItem, ItemState};
use hyperlog_core::log::{Graph, GraphItem, ItemState};
#[derive(Default)]
pub struct Engine {
@@ -218,10 +217,9 @@ impl Display for Engine {
mod test {
use std::collections::BTreeMap;
use hyperlog_core::log::{GraphItem, ItemState};
use similar_asserts::assert_eq;
use crate::log::{GraphItem, ItemState};
use super::Engine;
#[test]
@@ -249,7 +247,7 @@ mod test {
.create(
"kjuulh",
&["some-section"],
crate::log::GraphItem::Section(BTreeMap::default()),
GraphItem::Section(BTreeMap::default()),
)
.unwrap();
@@ -275,7 +273,7 @@ mod test {
.create(
"kjuulh",
&["some-section"],
crate::log::GraphItem::Section(BTreeMap::default()),
GraphItem::Section(BTreeMap::default()),
)
.unwrap();
@@ -283,7 +281,7 @@ mod test {
.create(
"kjuulh",
&["some-section", "some-sub-section"],
crate::log::GraphItem::Section(BTreeMap::default()),
GraphItem::Section(BTreeMap::default()),
)
.unwrap();

View File

@@ -1,13 +1,16 @@
#![feature(map_try_insert)]
#![feature(fn_traits)]
#![feature(let_chains)]
use std::{io::Stdout, time::Duration};
use std::io::Stdout;
use anyhow::{Context, Result};
use app::{render_app, App};
use commands::IntoCommand;
use commands::{Dispatch, IntoCommand, Receiver};
use components::graph_explorer::GraphExplorer;
use crossterm::event::{self, Event, KeyCode};
use hyperlog_core::state::State;
use core_state::State;
use crossterm::event::{Event, KeyCode, KeyEventKind};
use futures::{FutureExt, StreamExt};
use models::{EditMsg, Msg};
use ratatui::{backend::CrosstermBackend, Terminal};
@@ -19,7 +22,16 @@ pub(crate) mod app;
pub(crate) mod command_parser;
pub(crate) mod commands;
pub(crate) mod components;
pub(crate) mod state;
pub mod commander;
pub mod core_state;
pub mod shared_engine;
pub mod state;
mod engine;
mod events;
mod querier;
mod storage;
mod logging;
mod terminal;
@@ -33,13 +45,13 @@ pub async fn execute(state: State) -> Result<()> {
let state = SharedState::from(state);
let mut terminal = TerminalInstance::new()?;
run(&mut terminal, state).context("app loop failed")?;
run(&mut terminal, state).await.context("app loop failed")?;
Ok(())
}
fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>, state: SharedState) -> Result<()> {
let root = match state.querier.get_available_roots() {
async fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>, state: SharedState) -> Result<()> {
let root = match state.querier.get_available_roots_async().await? {
// TODO: maybe present choose root screen
Some(roots) => roots.first().cloned().unwrap(),
None => {
@@ -49,14 +61,25 @@ fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>, state: SharedState) ->
};
let mut graph_explorer = GraphExplorer::new(root.clone(), state.clone());
graph_explorer.update_graph()?;
graph_explorer.update_graph().await?;
let mut app = App::new(&root, state.clone(), graph_explorer);
let (dispatch, mut receiver) = commands::create_dispatch();
let mut event_stream = crossterm::event::EventStream::new();
loop {
terminal.draw(|f| render_app(f, &mut app))?;
if update(terminal, &mut app)?.should_quit() {
if update(
terminal,
&mut app,
&dispatch,
&mut receiver,
&mut event_stream,
)
.await?
.should_quit()
{
break;
}
}
@@ -75,53 +98,107 @@ impl UpdateConclusion {
}
}
fn update(
async fn update<'a>(
_terminal: &mut Terminal<CrosstermBackend<Stdout>>,
app: &mut App,
app: &mut App<'a>,
dispatch: &Dispatch,
receiver: &mut Receiver,
event_stream: &mut crossterm::event::EventStream,
) -> Result<UpdateConclusion> {
if event::poll(Duration::from_millis(250)).context("event poll failed")? {
if let Event::Key(key) = event::read().context("event read failed")? {
let mut cmd = match &app.mode {
app::Mode::View => match key.code {
KeyCode::Enter => app.update(Msg::Interact)?,
KeyCode::Char('l') => app.update(Msg::MoveRight)?,
KeyCode::Char('h') => app.update(Msg::MoveLeft)?,
KeyCode::Char('j') => app.update(Msg::MoveDown)?,
KeyCode::Char('k') => app.update(Msg::MoveUp)?,
KeyCode::Char('a') => {
// TODO: batch commands
app.update(Msg::OpenCreateItemDialog)?;
app.update(Msg::EnterInsertMode)?
}
KeyCode::Char('i') => app.update(Msg::EnterInsertMode)?,
KeyCode::Char(':') => app.update(Msg::EnterCommandMode)?,
_ => return Ok(UpdateConclusion(false)),
},
let cross_event = event_stream.next().fuse();
app::Mode::Command | app::Mode::Insert => match key.code {
KeyCode::Backspace => app.update(Msg::Edit(EditMsg::Delete))?,
KeyCode::Enter => app.update(Msg::Edit(EditMsg::InsertNewLine))?,
KeyCode::Tab => app.update(Msg::Edit(EditMsg::InsertTab))?,
KeyCode::Delete => app.update(Msg::Edit(EditMsg::DeleteNext))?,
KeyCode::Char(c) => app.update(Msg::Edit(EditMsg::InsertChar(c)))?,
KeyCode::Left => app.update(Msg::Edit(EditMsg::MoveLeft))?,
KeyCode::Right => app.update(Msg::Edit(EditMsg::MoveRight))?,
KeyCode::Esc => app.update(Msg::EnterViewMode)?,
_ => return Ok(UpdateConclusion(false)),
},
};
let mut handle_key_event = |maybe_event| -> anyhow::Result<UpdateConclusion> {
match maybe_event {
Some(Ok(e)) => {
if let Event::Key(key) = e
&& key.kind == KeyEventKind::Press
{
let mut cmd = match &app.mode {
app::Mode::View => match key.code {
KeyCode::Enter => app.update(Msg::Interact)?,
KeyCode::Char('l') => app.update(Msg::MoveRight)?,
KeyCode::Char('h') => app.update(Msg::MoveLeft)?,
KeyCode::Char('j') => app.update(Msg::MoveDown)?,
KeyCode::Char('k') => app.update(Msg::MoveUp)?,
KeyCode::Char('a') => {
// TODO: batch commands
app.update(Msg::OpenCreateItemDialog)?;
app.update(Msg::EnterInsertMode)?
}
KeyCode::Char('o') => {
// TODO: batch commands
app.update(Msg::OpenCreateItemDialogBelow)?;
app.update(Msg::EnterInsertMode)?
}
KeyCode::Char('i') => app.update(Msg::EnterInsertMode)?,
KeyCode::Char(':') => app.update(Msg::EnterCommandMode)?,
_ => return Ok(UpdateConclusion(false)),
},
loop {
let msg = cmd.into_command().execute();
match msg {
Some(msg) => {
if let Msg::QuitApp = msg {
return Ok(UpdateConclusion(true));
app::Mode::Command | app::Mode::Insert => match key.code {
KeyCode::Backspace => app.update(Msg::Edit(EditMsg::Delete))?,
KeyCode::Enter => app.update(Msg::Edit(EditMsg::InsertNewLine))?,
KeyCode::Tab => app.update(Msg::Edit(EditMsg::InsertTab))?,
KeyCode::Delete => app.update(Msg::Edit(EditMsg::DeleteNext))?,
KeyCode::Char(c) => app.update(Msg::Edit(EditMsg::InsertChar(c)))?,
KeyCode::Left => app.update(Msg::Edit(EditMsg::MoveLeft))?,
KeyCode::Right => app.update(Msg::Edit(EditMsg::MoveRight))?,
KeyCode::Esc => app.update(Msg::EnterViewMode)?,
_ => return Ok(UpdateConclusion(false)),
},
};
loop {
let msg = cmd.into_command().execute(dispatch.clone());
match msg {
Some(msg) => {
if let Msg::QuitApp = msg {
return Ok(UpdateConclusion(true));
}
cmd = app.update(msg)?;
}
None => break,
}
cmd = app.update(msg)?;
}
None => break,
}
}
Some(Err(e)) => {
tracing::warn!("failed to send event: {}", e);
}
None => {}
}
Ok(UpdateConclusion(false))
};
tokio::select! {
maybe_event = cross_event => {
let conclusion = handle_key_event(maybe_event)?;
return Ok(conclusion)
},
msg = receiver.next() => {
if let Some(msg) = msg {
if let Msg::QuitApp = msg {
return Ok(UpdateConclusion(true));
}
let mut cmd = app.update(msg)?;
loop {
let msg = cmd.into_command().execute(dispatch.clone());
match msg {
Some(msg) => {
if let Msg::QuitApp = msg {
return Ok(UpdateConclusion(true));
}
cmd = app.update(msg)?;
}
None => break,
}
}
}
}

View File

@@ -10,6 +10,7 @@ pub enum Msg {
MoveUp,
QuitApp,
OpenCreateItemDialog,
OpenCreateItemDialogBelow,
OpenEditItemDialog { item: GraphItem },
Interact,
@@ -20,11 +21,27 @@ pub enum Msg {
SubmitCommand { command: String },
Edit(EditMsg),
GraphUpdated(IOEvent<GraphItem>),
ItemCreated(IOEvent<()>),
ItemUpdated(IOEvent<()>),
SectionCreated(IOEvent<()>),
ItemToggled(IOEvent<()>),
OpenUpdateItemDialog(IOEvent<()>),
}
#[derive(Debug)]
pub enum IOEvent<T> {
Initialized,
Optimistic(T),
Success(T),
Failure(String),
}
impl IntoCommand for Msg {
fn into_command(self) -> crate::commands::Command {
Command::new(|| Some(self))
Command::new(|_| Some(self))
}
}

View File

@@ -0,0 +1,68 @@
use hyperlog_core::log::GraphItem;
use tonic::transport::Channel;
use crate::shared_engine::SharedEngine;
mod local;
mod remote;
#[derive(Clone)]
enum QuerierVariant {
Local(local::Querier),
Remote(remote::Querier),
}
#[derive(Clone)]
pub struct Querier {
variant: QuerierVariant,
}
impl Querier {
pub fn local(engine: &SharedEngine) -> Self {
Self {
variant: QuerierVariant::Local(local::Querier::new(engine)),
}
}
pub async fn remote(channel: Channel) -> anyhow::Result<Self> {
Ok(Self {
variant: QuerierVariant::Remote(remote::Querier::new(channel).await?),
})
}
pub fn get(
&self,
root: &str,
path: impl IntoIterator<Item = impl Into<String>>,
) -> Option<GraphItem> {
match &self.variant {
QuerierVariant::Local(querier) => querier.get(root, path),
QuerierVariant::Remote(_) => todo!(),
}
}
pub async fn get_async(
&self,
root: &str,
path: impl IntoIterator<Item = impl Into<String>>,
) -> anyhow::Result<Option<GraphItem>> {
match &self.variant {
QuerierVariant::Local(querier) => Ok(querier.get(root, path)),
QuerierVariant::Remote(querier) => querier.get(root, path).await,
}
}
pub fn get_available_roots(&self) -> Option<Vec<String>> {
match &self.variant {
QuerierVariant::Local(querier) => querier.get_available_roots(),
QuerierVariant::Remote(_) => todo!(),
}
}
pub async fn get_available_roots_async(&self) -> anyhow::Result<Option<Vec<String>>> {
match &self.variant {
QuerierVariant::Local(querier) => Ok(querier.get_available_roots()),
QuerierVariant::Remote(querier) => querier.get_available_roots().await,
}
}
}

View File

@@ -1,12 +1,17 @@
use crate::{log::GraphItem, shared_engine::SharedEngine};
use hyperlog_core::log::GraphItem;
use crate::shared_engine::SharedEngine;
#[derive(Clone)]
pub struct Querier {
engine: SharedEngine,
}
impl Querier {
pub fn new(engine: SharedEngine) -> Self {
Self { engine }
pub fn new(engine: &SharedEngine) -> Self {
Self {
engine: engine.clone(),
}
}
pub fn get_available_roots(&self) -> Option<Vec<String>> {
@@ -31,7 +36,10 @@ impl Querier {
path.len()
);
self.engine
.get(root, &path.iter().map(|i| i.as_str()).collect::<Vec<_>>())
let item = self
.engine
.get(root, &path.iter().map(|i| i.as_str()).collect::<Vec<_>>());
item
}
}

View File

@@ -0,0 +1,118 @@
use std::collections::BTreeMap;
use hyperlog_core::log::GraphItem;
use hyperlog_protos::hyperlog::{
graph_client::GraphClient, graph_item::Contents, GetAvailableRootsRequest, GetRequest,
};
use itertools::Itertools;
use tonic::transport::Channel;
#[allow(dead_code)]
#[derive(Clone)]
pub struct Querier {
channel: Channel,
}
#[allow(dead_code, unused_variables)]
impl Querier {
pub async fn new(channel: Channel) -> anyhow::Result<Self> {
Ok(Self { channel })
}
pub async fn get_available_roots(&self) -> anyhow::Result<Option<Vec<String>>> {
let channel = self.channel.clone();
let mut client = GraphClient::new(channel);
let request = tonic::Request::new(GetAvailableRootsRequest {});
let response = client.get_available_roots(request).await?;
let roots = response.into_inner();
if roots.roots.is_empty() {
Ok(None)
} else {
Ok(Some(roots.roots))
}
}
pub async fn get(
&self,
root: &str,
path: impl IntoIterator<Item = impl Into<String>>,
) -> anyhow::Result<Option<GraphItem>> {
let paths = path.into_iter().map(|i| i.into()).collect_vec();
tracing::debug!(
"quering: root:({}), path:({}), len: ({}))",
root,
paths.join("."),
paths.len()
);
let channel = self.channel.clone();
let mut client = GraphClient::new(channel);
let request = tonic::Request::new(GetRequest {
root: root.into(),
paths,
});
let response = client.get(request).await?;
let graph_item = response.into_inner();
if let Some(item) = graph_item.item {
let local_graph = transform_proto_to_local(&item);
Ok(local_graph)
} else {
Ok(None)
}
}
}
fn transform_proto_to_local(input: &hyperlog_protos::hyperlog::GraphItem) -> Option<GraphItem> {
match &input.contents {
Some(item) => match item {
Contents::User(user) => {
let mut items = BTreeMap::new();
for (key, value) in &user.items {
if let Some(item) = transform_proto_to_local(value) {
items.insert(key.clone(), item);
}
}
Some(GraphItem::User(items))
}
Contents::Section(section) => {
let mut items = BTreeMap::new();
for (key, value) in &section.items {
if let Some(item) = transform_proto_to_local(value) {
items.insert(key.clone(), item);
}
}
Some(GraphItem::Section(items))
}
Contents::Item(item) => Some(GraphItem::Item {
title: item.title.clone(),
description: item.description.clone(),
state: match &item.item_state {
Some(state) => match state {
hyperlog_protos::hyperlog::item_graph_item::ItemState::NotDone(_) => {
hyperlog_core::log::ItemState::NotDone
}
hyperlog_protos::hyperlog::item_graph_item::ItemState::Done(_) => {
hyperlog_core::log::ItemState::Done
}
},
None => hyperlog_core::log::ItemState::NotDone,
},
}),
},
None => None,
}
}

View File

@@ -1,6 +1,8 @@
use std::sync::{Arc, RwLock};
use crate::{engine::Engine, log::GraphItem};
use hyperlog_core::log::GraphItem;
use crate::engine::Engine;
#[derive(Clone)]
pub struct SharedEngine {

View File

@@ -1,6 +1,6 @@
use std::{ops::Deref, sync::Arc};
use hyperlog_core::state::State;
use crate::core_state::State;
#[derive(Clone)]
pub struct SharedState {

View File

@@ -149,10 +149,9 @@ impl Storage {
mod test {
use std::collections::BTreeMap;
use hyperlog_core::log::GraphItem;
use similar_asserts::assert_eq;
use crate::log::GraphItem;
use super::*;
#[test]

View File

@@ -7,6 +7,7 @@ repository = "https://git.front.kjuulh.io/kjuulh/hyperlog"
[dependencies]
hyperlog-core.workspace = true
hyperlog-tui.workspace = true
hyperlog-server = { workspace = true, optional = true }
anyhow.workspace = true
tokio.workspace = true
@@ -15,21 +16,17 @@ tracing-subscriber.workspace = true
clap.workspace = true
dotenv.workspace = true
axum.workspace = true
serde.workspace = true
serde_json.workspace = true
uuid.workspace = true
serde = { version = "1.0.201", features = ["derive"] }
sqlx = { version = "0.7.4", features = [
"runtime-tokio",
"tls-rustls",
"postgres",
"uuid",
"time",
] }
uuid = { version = "1.8.0", features = ["v4"] }
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
bus = "2.4.1"
dirs = "5.0.1"
[dev-dependencies]
similar-asserts = "1.5.0"
tempfile = "3.10.1"
[features]
default = []
include_server = ["dep:hyperlog-server"]

View File

@@ -1 +0,0 @@
-- Add migration script here

View File

@@ -1,22 +1,44 @@
use std::net::SocketAddr;
use clap::{Parser, Subcommand};
use hyperlog_core::{commander, state};
use crate::server::serve;
use clap::{Parser, Subcommand, ValueEnum};
use hyperlog_tui::{
commander,
core_state::{Backend, State},
};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Command {
#[command(subcommand)]
command: Option<Commands>,
#[arg(long, default_value = "local")]
backend: BackendArg,
}
#[derive(ValueEnum, Clone)]
enum BackendArg {
Local,
Remote,
}
impl From<BackendArg> for Backend {
fn from(value: BackendArg) -> Self {
match value {
BackendArg::Local => Backend::Local,
BackendArg::Remote => Backend::Remote,
}
}
}
#[derive(Subcommand)]
enum Commands {
#[cfg(feature = "include_server")]
Serve {
#[arg(env = "SERVICE_HOST", long, default_value = "127.0.0.1:3000")]
host: SocketAddr,
#[arg(env = "EXTERNAL_HOST", long, default_value = "127.0.0.1:3000")]
external_host: std::net::SocketAddr,
#[arg(env = "INTERNAL_HOST", long, default_value = "127.0.0.1:3001")]
internal_host: std::net::SocketAddr,
#[arg(env = "EXTERNAL_GRPC_HOST", long, default_value = "127.0.0.1:4000")]
external_grpc_host: std::net::SocketAddr,
},
Exec {
#[command(subcommand)]
@@ -70,58 +92,85 @@ pub async fn execute() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
}
let state = state::State::new()?;
let backend = cli.backend;
match cli.command {
Some(Commands::Serve { host }) => {
#[cfg(feature = "include_server")]
Some(Commands::Serve {
external_host,
internal_host,
external_grpc_host,
}) => {
tracing::info!("Starting service");
serve(host).await?;
hyperlog_server::serve(hyperlog_server::ServeOptions {
external_http: external_host,
internal_http: internal_host,
external_grpc: external_grpc_host,
})
.await?;
}
Some(Commands::Exec { commands }) => match commands {
ExecCommands::CreateRoot { root } => state
.commander
.execute(commander::Command::CreateRoot { root })?,
ExecCommands::CreateSection { root, path } => {
state.commander.execute(commander::Command::CreateSection {
root,
path: path
.unwrap_or_default()
.split('.')
.map(|s| s.to_string())
.filter(|s| !s.is_empty())
.collect::<Vec<String>>(),
})?
Some(Commands::Exec { commands }) => {
let state = State::new(backend.into()).await?;
match commands {
ExecCommands::CreateRoot { root } => {
state
.commander
.execute(commander::Command::CreateRoot { root })
.await?
}
ExecCommands::CreateSection { root, path } => {
state
.commander
.execute(commander::Command::CreateSection {
root,
path: path
.unwrap_or_default()
.split('.')
.map(|s| s.to_string())
.filter(|s| !s.is_empty())
.collect::<Vec<String>>(),
})
.await?
}
}
},
Some(Commands::Query { commands }) => match commands {
QueryCommands::Get { root, path } => {
let res = state.querier.get(
&root,
path.unwrap_or_default()
.split('.')
.filter(|s| !s.is_empty()),
);
}
Some(Commands::Query { commands }) => {
let state = State::new(backend.into()).await?;
match commands {
QueryCommands::Get { root, path } => {
let res = state.querier.get(
&root,
path.unwrap_or_default()
.split('.')
.filter(|s| !s.is_empty()),
);
let output = serde_json::to_string_pretty(&res)?;
let output = serde_json::to_string_pretty(&res)?;
println!("{}", output);
println!("{}", output);
}
}
},
}
Some(Commands::CreateRoot { name }) => {
let state = State::new(backend.into()).await?;
state
.commander
.execute(commander::Command::CreateRoot { root: name })?;
.execute(commander::Command::CreateRoot { root: name })
.await?;
println!("Root was successfully created, now run:\n\n$ hyperlog");
}
Some(Commands::Info {}) => {
let state = State::new(backend.into()).await?;
println!("graph stored at: {}", state.storage.info()?)
}
Some(Commands::ClearLock {}) => {
let state = State::new(backend.into()).await?;
state.storage.clear_lock_file();
println!("cleared lock file");
}
None => {
let state = State::new(backend.into()).await?;
hyperlog_tui::execute(state).await?;
}
}

View File

@@ -1,6 +1,4 @@
mod cli;
pub(crate) mod server;
pub(crate) mod state;
#[tokio::main]
async fn main() -> anyhow::Result<()> {

View File

@@ -1,42 +1 @@
use std::{net::SocketAddr, sync::Arc};
use axum::{extract::MatchedPath, http::Request, routing::get, Router};
use tower_http::trace::TraceLayer;
use crate::state::{SharedState, State};
async fn root() -> &'static str {
"Hello, hyperlog!"
}
pub async fn serve(host: SocketAddr) -> anyhow::Result<()> {
let state = SharedState(Arc::new(State::new().await?));
let app = Router::new()
.route("/", get(root))
.with_state(state.clone())
.layer(
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
// Log the matched route's path (with placeholders not filled in).
// Use request.uri() or OriginalUri if you want the real path.
let matched_path = request
.extensions()
.get::<MatchedPath>()
.map(MatchedPath::as_str);
tracing::info_span!(
"http_request",
method = ?request.method(),
matched_path,
some_other_field = tracing::field::Empty,
)
}), // ...
);
tracing::info!("listening on {}", host);
let listener = tokio::net::TcpListener::bind(host).await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
Ok(())
}

View File

@@ -1,37 +1 @@
use std::{ops::Deref, sync::Arc};
use anyhow::Context;
use sqlx::{Pool, Postgres};
#[derive(Clone)]
pub struct SharedState(pub Arc<State>);
impl Deref for SharedState {
type Target = Arc<State>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct State {
pub _db: Pool<Postgres>,
}
impl State {
pub async fn new() -> anyhow::Result<Self> {
let db = sqlx::PgPool::connect(
&std::env::var("DATABASE_URL").context("DATABASE_URL is not set")?,
)
.await?;
sqlx::migrate!("migrations/crdb")
.set_locking(false)
.run(&db)
.await?;
let _ = sqlx::query("SELECT 1;").fetch_one(&db).await?;
Ok(Self { _db: db })
}
}

View File

@@ -15,3 +15,9 @@ please:
api_url: https://git.front.kjuulh.io
actions:
rust:
scripts:
dev:
type: shell
install:
type: shell

484
demo.cast
View File

@@ -1,187 +1,297 @@
{"version": 2, "width": 121, "height": 31, "timestamp": 1715413204, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}}
[0.28385, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[0.356577, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0m\u001b[1;31mrs \u001b[0m\r\n\u001b[38;2;255;153;102m\u001b[0m \u001b[K"]
[0.35759, "o", "\u001b[6 q"]
[0.358564, "o", "\u001b[6 q"]
[0.358794, "o", "\u001b[?2004h"]
[1.196152, "o", "c"]
[1.200489, "o", "\b\u001b[32mc\u001b[39m"]
[1.241408, "o", "\b\u001b[32mc\u001b[39m\u001b[90mlear\u001b[39m\b\b\b\b"]
[1.340197, "o", "\b\u001b[32mc\u001b[32ma\u001b[39m\u001b[39m \u001b[39m \u001b[39m \b\b\b"]
[1.369897, "o", "\b\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31ma\u001b[0m\u001b[39m"]
[1.37249, "o", "\u001b[90mrgo run\u001b[39m\b\b\b\b\b\b\b"]
[1.424331, "o", "\b\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31ma\u001b[1m\u001b[31mr\u001b[0m\u001b[39m"]
[1.592372, "o", "\b\u001b[1m\u001b[31mr\u001b[1m\u001b[31mg\u001b[0m\u001b[39m"]
[1.666639, "o", "\b\u001b[1m\u001b[31mg\u001b[1m\u001b[31mo\u001b[0m\u001b[39m"]
[1.671179, "o", "\b\b\b\b\b\u001b[0m\u001b[32mc\u001b[0m\u001b[32ma\u001b[0m\u001b[32mr\u001b[0m\u001b[32mg\u001b[0m\u001b[32mo\u001b[39m"]
[2.080073, "o", "\b\u001b[32mo\u001b[32m \u001b[39m"]
[2.084222, "o", "\b\b\u001b[32mo\u001b[39m\u001b[39m "]
[2.246919, "o", "\u001b[39mr"]
[2.252663, "o", "\b\u001b[4mr\u001b[24m"]
[2.347417, "o", "\b\u001b[4mr\u001b[39m\u001b[4mu\u001b[24m"]
[2.351929, "o", "\b\b\u001b[24mr\u001b[24mu"]
[2.501908, "o", "\u001b[39mn"]
[2.800437, "o", "\u001b[?1l\u001b>"]
[2.800875, "o", "\u001b[?2004l"]
[2.807783, "o", "\u001b[0 q"]
[2.808107, "o", "\r\r\n"]
[3.35867, "o", "\u001b[1m\u001b[32m Finished\u001b[0m `dev` profile [unoptimized + debuginfo] target(s) in 0.49s\r\n"]
[3.365514, "o", "\u001b[1m\u001b[32m Running\u001b[0m `target/debug/hyperlog`\r\n"]
[3.740156, "o", "\u001b[?1049h"]
[3.743543, "o", "\u001b[1;1H\u001b[38;5;2mhyperlog\u001b[2;1H\u001b[39m─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[3;1Hsomething\u001b[3;11H~\u001b[3;13H(items:\u001b[3;21H3)\u001b[4;5Ha\u001b[4;7H~\u001b[4;9H(items:\u001b[4;17H2)\u001b[5;5H\u001b[38;5;8m...\u001b[6;5H\u001b[39mc\u001b[6;7H~\u001b[6;9H(items:\u001b[6;17H0)\u001b[31;2H--\u001b[31;5HVIEW\u001b[31;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[4.001932, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[4.260086, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[4.458203, "o", "\u001b[3;1H\u001b[38;2;255;165;0msomething ~ (items: 3)\u001b[5;5H\u001b[39m \u001b[5;9H[\u001b[5;11H]\u001b[5;13Hitem\u001b[6;5H \u001b[6;7H \u001b[6;9H[ ] something\u001b[7;5Hb\u001b[7;7H~\u001b[7;9H(items:\u001b[7;17H1)\u001b[8;9H[\u001b[8;11H]\u001b[8;13Hitem\u001b[9;5Hc\u001b[9;7H~\u001b[9;9H(items:\u001b[9;17H0)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[4.716099, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[4.974526, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[5.235168, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[5.471231, "o", "\u001b[31;1H: \u001b[31;5H \u001b[31;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[5.670365, "o", "\u001b[31;2Hs\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[5.776552, "o", "\u001b[31;3Hh\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[5.907952, "o", "\u001b[31;4Ho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[6.035263, "o", "\u001b[31;5Ht\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[6.187704, "o", "\u001b[31;6H-\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[6.299824, "o", "\u001b[31;7Ha\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[6.438169, "o", "\u001b[31;8Hl\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[6.577971, "o", "\u001b[31;9Hl\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[6.836633, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[6.968556, "o", "\u001b[31;9H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.106562, "o", "\u001b[31;8H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.251411, "o", "\u001b[31;7H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.395354, "o", "\u001b[31;6H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.54286, "o", "\u001b[31;5H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.731859, "o", "\u001b[31;5Hw\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.989235, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[8.121554, "o", "\u001b[31;6H-\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[8.299653, "o", "\u001b[31;7Ha\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[8.399794, "o", "\u001b[31;8Hl\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[8.549891, "o", "\u001b[31;9Hl\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[8.785214, "o", "\u001b[9;17H5\u001b[10;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[10;13Hitem\u001b[11;9H\u001b[38;5;8m...\u001b[12;9H\u001b[39m[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[12;13Hitem-d\u001b[31;1H -- VIEW --\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[9.044234, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[9.303396, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[9.554819, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[9.813405, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.073436, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.077833, "o", "\u001b[3;1Hsomething ~ (items: 3)\u001b[5;5H\u001b[38;5;8m...\u001b[5;9H\u001b[39m \u001b[5;11H \u001b[5;13H \u001b[6;5Hc\u001b[6;7H~\u001b[6;9H(items: 5) \u001b[7;5H \u001b[7;7H \u001b[7;9H \u001b[7;17H \u001b[8;9H \u001b[8;11H \u001b[8;13H \u001b[9;5H \u001b[9;7H \u001b[9;9H \u001b[9;17H \u001b[10;9H \u001b[10;13H \u001b[11;9H \u001b[12;9H \u001b[12;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.33073, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.590022, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.750514, "o", "\u001b[3;1H\u001b[38;2;255;165;0msomething ~ (items: 3)\u001b[5;5H\u001b[39m \u001b[5;9H[\u001b[5;11H]\u001b[5;13Hitem\u001b[6;5H \u001b[6;7H \u001b[6;9H[ ] something\u001b[7;5Hb\u001b[7;7H~\u001b[7;9H(items:\u001b[7;17H1)\u001b[8;9H[\u001b[8;11H]\u001b[8;13Hitem\u001b[9;5Hc\u001b[9;7H~\u001b[9;9H(items:\u001b[9;17H5)\u001b[10;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[10;13Hitem\u001b[11;9H\u001b[38;5;8m...\u001b[12;9H\u001b[39m[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[12;13Hitem-d\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.009542, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.101987, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.310184, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.569878, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.646194, "o", "\u001b[3;1Hsomething ~ (items: 3)\u001b[4;5H\u001b[38;2;255;165;0ma ~ (items: 2)\u001b[7;5H\u001b[39m \u001b[7;7H \u001b[7;9H \u001b[7;17H \u001b[8;5Hb\u001b[8;7H~\u001b[8;9H(items: 1)\u001b[9;5H \u001b[9;7H \u001b[9;9H[ ] item \u001b[10;5Hc\u001b[10;7H~\u001b[10;9H(items: 5)\u001b[11;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[11;13Hitem\u001b[12;9H\u001b[38;5;8m...\u001b[12;13H\u001b[39m \u001b[13;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[13;13Hitem-d\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.905614, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.9512, "o", "\u001b[4;5Ha ~ (items: 2)\u001b[7;5H\u001b[38;2;255;165;0mb ~ (items: 1)\u001b[8;5H\u001b[39m \u001b[8;7H \u001b[8;9H[ ] item \u001b[9;9H \u001b[9;11H \u001b[9;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.171517, "o", "\u001b[7;5Hb ~ (items: 1)\u001b[9;5H\u001b[38;2;255;165;0mc ~ (items: 5)\u001b[10;5H\u001b[39m \u001b[10;7H \u001b[10;9H[\u001b[38;2;127;255;0mx\u001b[39m] item \u001b[11;17H-a\u001b[12;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[12;13Hitem-b\u001b[13;18Hc\u001b[14;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[14;13Hitem-d\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.428831, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.687811, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.710816, "o", "\u001b[4;5Hc\u001b[4;17H5\u001b[5;9H\u001b[38;2;255;165;0m[x] item\u001b[6;10H\u001b[38;2;127;255;0mx\u001b[6;13H\u001b[39mitem-a \u001b[7;5H \u001b[7;7H \u001b[7;9H[\u001b[38;2;127;255;0mx\u001b[39m] item-b\u001b[8;10H\u001b[38;2;127;255;0mx\u001b[8;17H\u001b[39m-c\u001b[9;5H [\u001b[38;2;127;255;0mx\u001b[39m] item-d\u001b[10;9H \u001b[10;13H \u001b[11;9H \u001b[11;13H \u001b[12;9H \u001b[12;13H \u001b[13;9H \u001b[13;13H \u001b[14;9H \u001b[14;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.970938, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.229238, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.402499, "o", "\u001b[5;10H\u001b[38;2;255;165;0m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.661623, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.709679, "o", "\u001b[5;9H[ ] item\u001b[6;9H\u001b[38;2;255;165;0m[x] item-a\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.969177, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.980927, "o", "\u001b[6;10H\u001b[38;2;255;165;0m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[14.240333, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[14.453095, "o", "\u001b[4;5Ha\u001b[4;17H2\u001b[6;9H[ ] something\u001b[7;5Hb\u001b[7;7H~\u001b[7;9H(items: 1)\u001b[8;10H \u001b[8;17H \u001b[9;5H\u001b[38;2;255;165;0mc ~ (items: 5)\u001b[10;9H\u001b[39m[\u001b[10;11H]\u001b[10;13Hitem\u001b[11;9H[\u001b[11;11H]\u001b[11;13Hitem-a\u001b[12;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[12;13Hitem-b\u001b[13;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[13;13Hitem-c\u001b[14;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[14;13Hitem-d\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[14.638745, "o", "\u001b[3;1H\u001b[38;2;255;165;0msomething ~ (items: 3)\u001b[9;5H\u001b[39mc ~ (items: 5)\u001b[11;9H\u001b[38;5;8m...\u001b[11;13H\u001b[39m \u001b[12;18Hd\u001b[13;9H \u001b[13;13H \u001b[14;9H \u001b[14;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[14.897952, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[15.0327, "o", "\u001b[31;1H: \u001b[31;5H \u001b[31;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[15.291396, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[15.549911, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[15.658184, "o", "\u001b[31;2Hh\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[15.809793, "o", "\u001b[31;3Hi\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[15.949529, "o", "\u001b[31;4Hd\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[16.096886, "o", "\u001b[31;5He\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[16.254773, "o", "\u001b[31;6H-\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[16.470747, "o", "\u001b[31;7Hd\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[16.551481, "o", "\u001b[31;8Ho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[16.709128, "o", "\u001b[31;9Hn\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[16.827375, "o", "\u001b[31;10He\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.005258, "o", "\u001b[9;17H2\u001b[11;9H[ ]\u001b[11;13Hitem-a\u001b[12;9H \u001b[12;13H \u001b[31;1H -- VIEW --\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.263877, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.523133, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.570586, "o", "\u001b[3;1Hsomething ~ (items: 3)\u001b[4;5H\u001b[38;2;255;165;0ma ~ (items: 2)\u001b[7;5H\u001b[39m \u001b[7;7H \u001b[7;9H \u001b[7;17H \u001b[8;5Hb\u001b[8;7H~\u001b[8;9H(items: 1)\u001b[9;5H \u001b[9;7H \u001b[9;9H[ ] item \u001b[10;5Hc\u001b[10;7H~\u001b[10;9H(items: 2)\u001b[11;17H \u001b[12;9H[\u001b[12;11H]\u001b[12;13Hitem-a\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.829292, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.986953, "o", "\u001b[4;5Ha ~ (items: 2)\u001b[7;5H\u001b[38;2;255;165;0mb ~ (items: 1)\u001b[8;5H\u001b[39m \u001b[8;7H \u001b[8;9H[ ] item \u001b[9;9H \u001b[9;11H \u001b[9;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.191003, "o", "\u001b[7;5Hb ~ (items: 1)\u001b[9;5H\u001b[38;2;255;165;0mc ~ (items: 2)\u001b[10;5H\u001b[39m \u001b[10;7H \u001b[10;9H[ ] item \u001b[11;17H-a\u001b[12;9H \u001b[12;11H \u001b[12;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.44961, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.709575, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.968623, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[19.193329, "o", "\u001b[31;1H: \u001b[31;5H \u001b[31;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[19.45148, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[19.45588, "o", "\u001b[31;2Hs\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[19.506083, "o", "\u001b[31;3Hh\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[19.767374, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[19.772362, "o", "\u001b[31;4Hw\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.031712, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.156021, "o", "\u001b[31;4H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.414819, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.422959, "o", "\u001b[31;4Ho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.564511, "o", "\u001b[31;5Hw\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.686556, "o", "\u001b[31;6H-\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.891174, "o", "\u001b[31;7Ha\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[21.014086, "o", "\u001b[31;8Hl\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[21.168709, "o", "\u001b[31;9Hl\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[21.427999, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[21.436176, "o", "\u001b[9;17H\u001b[38;2;255;165;0m5\u001b[12;9H\u001b[39m[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[12;13Hitem-b\u001b[13;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[13;13Hitem-c\u001b[14;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[14;13Hitem-d\u001b[31;1H -- VIEW --\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[21.695984, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[21.957095, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.04995, "o", "\u001b[7;5H\u001b[38;2;255;165;0mb ~ (items: 1)\u001b[9;5H\u001b[39m \u001b[10;5Hc\u001b[10;7H~\u001b[10;9H(items: 5)\u001b[11;17H \u001b[12;9H\u001b[38;5;8m...\u001b[12;13H\u001b[39m \u001b[13;18Hd\u001b[14;9H \u001b[14;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.311034, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.568875, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.795873, "o", "\u001b[4;5Hb\u001b[4;17H1\u001b[5;9H\u001b[38;2;255;165;0m[ ] item\u001b[6;9H\u001b[39m \u001b[6;11H \u001b[6;13H \u001b[7;5H \u001b[8;9H \u001b[8;11H \u001b[8;13H \u001b[10;5H \u001b[10;7H \u001b[10;9H \u001b[10;17H \u001b[11;9H \u001b[11;11H \u001b[11;13H \u001b[12;9H \u001b[13;9H \u001b[13;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.054478, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.24198, "o", "\u001b[5;10H\u001b[38;2;255;165;0mx\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.500408, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.646615, "o", "\u001b[4;5Ha\u001b[4;17H2\u001b[5;9H[ ] item\u001b[6;9H[\u001b[6;11H]\u001b[6;13Hsomething\u001b[7;5H\u001b[38;2;255;165;0mb ~ (items: 1)\u001b[8;9H\u001b[39m[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[8;13Hitem\u001b[10;5Hc\u001b[10;7H~\u001b[10;9H(items:\u001b[10;17H5)\u001b[11;9H[\u001b[11;11H]\u001b[11;13Hitem\u001b[12;9H\u001b[38;5;8m...\u001b[13;9H\u001b[39m[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[13;13Hitem-d\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.90405, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.162272, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.335133, "o", "\u001b[31;1H: \u001b[31;5H \u001b[31;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.590424, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.825885, "o", "\u001b[31;2Hh\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.994253, "o", "\u001b[31;3Hi\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.102804, "o", "\u001b[31;4Hd\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.2455, "o", "\u001b[31;5He\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.327672, "o", "\u001b[31;6H-\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.47797, "o", "\u001b[31;7Hd\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.603255, "o", "\u001b[31;8Ho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.749521, "o", "\u001b[31;9Hn\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.862533, "o", "\u001b[31;10He\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.026357, "o", "\u001b[7;17H\u001b[38;2;255;165;0m0\u001b[8;5H\u001b[39mc\u001b[8;7H~\u001b[8;9H(items: 2)\u001b[9;9H[\u001b[9;11H]\u001b[9;13Hitem\u001b[10;5H \u001b[10;7H \u001b[10;9H[ ] item-a\u001b[11;9H \u001b[11;11H \u001b[11;13H \u001b[12;9H \u001b[13;9H \u001b[13;13H \u001b[31;1H -- VIEW --\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.285752, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.546241, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.80384, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.061451, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.318053, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.435387, "o", "\u001b[31;1H: \u001b[31;5H \u001b[31;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.678991, "o", "\u001b[31;2Hq\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.801458, "o", "\u001b[?1049l\u001b[?25h"]
[27.803865, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[27.863445, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0m\u001b[1;31mrs \u001b[0m\u001b[33m25s\u001b[0m \r\n\u001b[38;2;255;153;102m\u001b[0m \u001b[K"]
[27.864508, "o", "\u001b[6 q"]
[27.865397, "o", "\u001b[6 q"]
[27.865567, "o", "\u001b[?2004h"]
[28.397871, "o", "c"]
[28.402538, "o", "\b\u001b[32mc\u001b[39m"]
[28.440037, "o", "\b\u001b[32mc\u001b[39m\u001b[90margo run\u001b[39m\u001b[8D"]
[28.478861, "o", "\b\u001b[32mc\u001b[32ml\u001b[39m\u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \b\b\b\b\b\b\b"]
[28.484213, "o", "\b\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31ml\u001b[0m\u001b[39m"]
[28.505375, "o", "\u001b[90mear\u001b[39m\b\b\b"]
[28.619796, "o", "\b\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31ml\u001b[1m\u001b[31me\u001b[0m\u001b[39m"]
[28.674043, "o", "\b\u001b[1m\u001b[31me\u001b[1m\u001b[31ma\u001b[0m\u001b[39m"]
[28.762176, "o", "\b\u001b[1m\u001b[31ma\u001b[1m\u001b[31mr\u001b[0m\u001b[39m"]
[28.764782, "o", "\b\b\b\b\b\u001b[0m\u001b[32mc\u001b[0m\u001b[32ml\u001b[0m\u001b[32me\u001b[0m\u001b[32ma\u001b[0m\u001b[32mr\u001b[39m"]
[28.816778, "o", "\u001b[?1l\u001b>"]
[28.817025, "o", "\u001b[?2004l"]
[28.821673, "o", "\u001b[0 q"]
[28.82202, "o", "\r\r\n"]
[28.866023, "o", "\u001b[3J\u001b[H\u001b[2J"]
[28.866306, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[28.910374, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0m\u001b[1;31mrs \u001b[0m\r\n\u001b[38;2;255;153;102m\u001b[0m \u001b[K"]
[28.911365, "o", "\u001b[6 q"]
[28.912156, "o", "\u001b[6 q"]
[28.91233, "o", "\u001b[?2004h"]
[29.510648, "o", "\u001b[?2004l\r\r\n"]
{"version": 2, "width": 123, "height": 33, "timestamp": 1715717726, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}}
[0.289876, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[0.363463, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0mis \u001b[1;38;5;208m📦 \u001b[0m\u001b[1;38;5;208mv0.1.0\u001b[0m \u001b[1;31mrs \u001b[0m\r\n\u001b[38;2;255;153;102m\u001b[0m \u001b[K"]
[0.364423, "o", "\u001b[6 q"]
[0.365312, "o", "\u001b[6 q"]
[0.365501, "o", "\u001b[?2004h"]
[1.080115, "o", "c"]
[1.082107, "o", "\b\u001b[32mc\u001b[39m"]
[1.101402, "o", "\b\u001b[32mc\u001b[39m\u001b[90margo run --release -- --backend remote\u001b[39m\u001b[38D"]
[1.169782, "o", "\b\u001b[32mc\u001b[32ma\u001b[39m"]
[1.189287, "o", "\b\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31ma\u001b[0m\u001b[39m"]
[1.249073, "o", "\b\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31ma\u001b[1m\u001b[31mr\u001b[0m\u001b[39m"]
[1.43174, "o", "\b\u001b[1m\u001b[31mr\u001b[1m\u001b[31mg\u001b[0m\u001b[39m"]
[1.578255, "o", "\b\u001b[1m\u001b[31mg\u001b[1m\u001b[31mo\u001b[0m\u001b[39m"]
[1.580674, "o", "\b\b\b\b\b\u001b[0m\u001b[32mc\u001b[0m\u001b[32ma\u001b[0m\u001b[32mr\u001b[0m\u001b[32mg\u001b[0m\u001b[32mo\u001b[39m"]
[2.050954, "o", "\u001b[39m \u001b[39mr\u001b[39mu\u001b[39mn\u001b[39m \u001b[39m-\u001b[39m-\u001b[39mr\u001b[39me\u001b[39ml\u001b[39me\u001b[39ma\u001b[39ms\u001b[39me\u001b[39m \u001b[39m-\u001b[39m-\u001b[39m \u001b[39m-\u001b[39m-\u001b[39mb\u001b[39ma\u001b[39mc\u001b[39mk\u001b[39me\u001b[39mn\u001b[39md\u001b[39m \u001b[39mr\u001b[39me\u001b[39mm\u001b[39mo\u001b[39mt\u001b[39me"]
[2.553848, "o", "\u001b[?1l\u001b>"]
[2.554218, "o", "\u001b[?2004l"]
[2.56397, "o", "\u001b[0 q"]
[2.564341, "o", "\r\r\n"]
[2.891881, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `std::net::SocketAddr`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog/src/cli.rs:1:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse std::net::SocketAddr;\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_imports)]` on by default\u001b[0m\r\n\r\n"]
[2.892626, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `GraphItem`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_item.rs:1:26\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse hyperlog_core::log::{GraphItem, ItemState};\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_imports)]` on by default\u001b[0m\r\n\r\n"]
[2.892638, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `hyperlog_core::log::GraphItem`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_section.rs:1:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse hyperlog_core::log::GraphItem;\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m\r\n\r\n"]
[2.892645, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused imports: `GraphItem`, `ItemState`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_available_roots.rs:1:26\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse hyperlog_core::log::{GraphItem, ItemState};\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\r\n"]
[2.892769, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `sqlx::types::Json`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_available_roots.rs:2:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m2\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse sqlx::types::Json;\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `root`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:102:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m102\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m root,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `root: _`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_variables)]` on by default\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variab"]
[2.892853, "o", "le: `path`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:103:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m103\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m path,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `path: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `title`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:104:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m104\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m title,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `title: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `description`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:105:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;"]
[2.89292, "o", "12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m105\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m description,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `description: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `state`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:106:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m106\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m state,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `state: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `root`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:108:35\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m108\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::ToggleItem { root, p"]
[2.892978, "o", "ath } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `root: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `path`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:108:41\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m108\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::ToggleItem { root, path } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `path: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `root`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:109:29\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m109\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::Move { root, src, dest } => todo!(),\u001b[0m\r\n\u001b[0m "]
[2.893044, "o", "\u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `root: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `src`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:109:35\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m109\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::Move { root, src, dest } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `src: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `dest`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:109:40\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m109\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::Move { root, src, dest } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m "]
[2.89314, "o", " \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `dest: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `req`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_available_roots.rs:26:33\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m26\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_req`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `root_name` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_item.rs:31:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m29\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Root {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[3"]
[2.893227, "o", "8;5;12m----\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m30\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m31\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m root_name: String,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(dead_code)]` on by default\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `id` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_item.rs:36:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m35\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Section {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m-------\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m36\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0"]
[2.893323, "o", "m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `root_name` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_section.rs:19:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m17\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Root {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m----\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m18\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m19\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m root_name: String,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `id` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_graph.rs:37:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m36\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Node {\u001b[0m\r\n\u001b[0m \u001b"]
[2.893379, "o", "[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m----\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m37\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `Node` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: multiple associated items are never used\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_graph.rs:152:16\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m151\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m impl Engine {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m-----------\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12massociated items in this implementation\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m152\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b"]
[2.893458, "o", "[0m pub fn engine_from_str(input: &str) -> anyhow::Result<Self> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m158\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn to_str(&self) -> anyhow::Result<String> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m219\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn get_mut(&mut self, root: &str, path: &[&str]) -> Option<&mut GraphItem> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m225\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn take(&mut self, root: &str, path: &[&str]) -> Option<GraphItem> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m231\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m"]
[2.893531, "o", "\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn section_move(\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m268\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn delete(&mut self, root: &str, path: &[&str]) -> anyhow::Result<()> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m274\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn toggle_item(&mut self, root: &str, path: &[&str]) -> anyhow::Result<()> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m290\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn update_item(\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m341\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m "]
[2.893617, "o", " pub fn get_roots(&self) -> Option<Vec<String>> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\r\n"]
[2.894153, "o", "\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m:\u001b[0m `hyperlog` (bin \"hyperlog\") generated 1 warning (run `cargo fix --bin \"hyperlog\"` to apply 1 suggestion)\r\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m:\u001b[0m `hyperlog-server` (lib) generated 20 warnings (run `cargo fix --lib -p hyperlog-server` to apply 4 suggestions)\r\n\u001b[1m\u001b[32m Finished\u001b[0m `release` profile [optimized] target(s) in 0.28s\r\n"]
[2.903509, "o", "\u001b[1m\u001b[32m Running\u001b[0m `target/release/hyperlog --backend remote`\r\n"]
[3.072772, "o", "\u001b[?1049h"]
[3.088809, "o", "\u001b[1;1H\u001b[38;5;2mhyperlog\u001b[2;1H\u001b[39m───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[3;1Hsomething\u001b[3;11H~\u001b[3;13H(items:\u001b[3;21H2)\u001b[4;5Hi-can-do-this\u001b[4;19H~\u001b[4;21H(items:\u001b[4;29H1)\u001b[5;5Hsomething\u001b[5;15H~\u001b[5;17H(items:\u001b[5;25H3)\u001b[33;2H--\u001b[33;5HVIEW\u001b[33;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[3.99447, "o", "\u001b[3;1H\u001b[38;2;255;165;0msomething ~ (items: 2)\u001b[5;5H\u001b[39m [ ] with-\u001b[5;23H \u001b[5;25H \u001b[6;5Hsomething\u001b[6;15H~\u001b[6;17H(items:\u001b[6;25H3)\u001b[7;9H[\u001b[7;11H]\u001b[7;13Hand-another\u001b[8;9H\u001b[38;5;8m...\u001b[9;9H\u001b[39m[\u001b[9;11H]\u001b[9;13Hitem\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[4.842467, "o", "\u001b[3;1Hsomething ~ (items: 2)\u001b[5;5Hsomething ~ (\u001b[5;23H:\u001b[5;25H3)\u001b[6;5H \u001b[6;15H \u001b[6;17H \u001b[6;25H \u001b[7;9H \u001b[7;11H \u001b[7;13H \u001b[8;9H \u001b[9;9H \u001b[9;11H \u001b[9;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[5.187689, "o", "\u001b[33;1H: \u001b[33;5H \u001b[33;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[5.37781, "o", "\u001b[33;2Hc\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[5.583938, "o", "\u001b[33;3Hs\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[5.744397, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[6.118579, "o", "\u001b[33;5Hi\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[6.262015, "o", "\u001b[33;6Ht\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.099969, "o", "\u001b[33;7H-\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.211383, "o", "\u001b[33;8Hw\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.326906, "o", "\u001b[33;9Ho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.40033, "o", "\u001b[33;10Hr\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.546151, "o", "\u001b[33;11Hk\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.670118, "o", "\u001b[33;12Hs\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.876666, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.877584, "o", "\u001b[33;1H \u001b[33;5H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.878029, "o", "\u001b[33;2H--\u001b[33;5HVIEW\u001b[33;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.878496, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.891922, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.892175, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.901476, "o", "\u001b[3;1Hit-works ~ (items: 0) \u001b[4;1Hsomething ~ (items:\u001b[4;21H2) \u001b[4;29H \u001b[5;5Hi-can-do-this ~ (items:\u001b[5;29H1)\u001b[6;5Hsomething\u001b[6;15H~\u001b[6;17H(items:\u001b[6;25H3)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.901907, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[8.476726, "o", "\u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 0)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[9.1102, "o", "\u001b[1;10H~\u001b[1;12Hcreate\u001b[1;19Hitem\u001b[3;1H\u001b[38;5;8mpath: kjuulh.it-works \u001b[4;1H \u001b[5;1H\u001b[39m┌title────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\u001b[6;1H│\u001b[38;5;0m\u001b[48;5;5m \u001b[6;5H\u001b[39m\u001b[49m \u001b[6;15H \u001b[6;17H \u001b[6;25H \u001b[6;123H│\u001b[7;1H└───────────────────────────────────────────────────────────────────────────────────"]
[9.110401, "o", "──────────────────────────────────────┘\u001b[8;1H\u001b[38;5;8m┌description──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\u001b[9;1H│ \u001b[10;1H└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\u001b[33;5H\u001b[39mEDIT\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?"]
[9.110539, "o", "25l"]
[9.875008, "o", "\u001b[6;2HI\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[9.979676, "o", "\u001b[6;3H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.159581, "o", "\u001b[6;4Hc\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.224824, "o", "\u001b[6;5Ha\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.344486, "o", "\u001b[6;6Hn\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.424478, "o", "\u001b[6;7H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.616037, "o", "\u001b[6;8Ha\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.717012, "o", "\u001b[6;9Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.867539, "o", "\u001b[6;10Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.965244, "o", "\u001b[6;11H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.038338, "o", "\u001b[6;12Hi\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.146079, "o", "\u001b[6;13Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.224729, "o", "\u001b[6;14He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.298028, "o", "\u001b[6;15Hm\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.493598, "o", "\u001b[6;16Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.859838, "o", "\u001b[6;17H \u001b[33;5HVIEW\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.18879, "o", "\u001b[33;1H: \u001b[33;5H \u001b[33;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.27651, "o", "\u001b[33;2Hw\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.348568, "o", "\u001b[33;3Hq\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.518587, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.519161, "o", "\u001b[1;10H \u001b[1;12H \u001b[1;19H \u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 0)\u001b[39m \u001b[4;1Hsomething ~ (items: 2) \u001b[5;1H i-can-do-this ~ (items: 1) \u001b[6;1H \u001b[6;4H something ~ (items:\u001b[6;25H3)\u001b[6;123H \u001b[7;1H \u001b[8;1H \u001b[9;1H \u001b[10;1H \u001b[33;1H \u001b[39"]
[12.519414, "o", "m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.51976, "o", "\u001b[33;2H--\u001b[33;5HVIEW\u001b[33;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.520255, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.542732, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.542941, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.552336, "o", "\u001b[3;20H\u001b[38;2;255;165;0m1\u001b[4;1H\u001b[39m [ ] I-can-add-items\u001b[5;5H \u001b[5;19H \u001b[5;21H \u001b[5;29H \u001b[6;1Hsomething ~ (items: 2) \u001b[6;25H \u001b[7;5Hi-can-do-this\u001b[7;19H~\u001b[7;21H(items:\u001b[7;29H1)\u001b[8;5Hsomething\u001b[8;15H~\u001b[8;17H(items:\u001b[8;25H3)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.552493, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.299017, "o", "\u001b[3;1Hit-works ~ (items: 1)\u001b[4;5H\u001b[38;2;255;165;0m[ ] I-can-add-items\u001b[6;1H\u001b[39m \u001b[6;11H \u001b[6;13H \u001b[6;21H \u001b[7;5H \u001b[7;19H \u001b[7;21H \u001b[7;29H \u001b[8;5H \u001b[8;15H \u001b[8;17H \u001b[8;25H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.686285, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.68677, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.74053, "o", "\u001b[31mWell, this is embarrassing.\n\nhyperlog-tui had a problem and crashed. To help us diagnose the problem you can send us a crash report.\n\nWe have generated a report file at \"/var/folders/qp/6jwp97j95qg2ks56vrxt_l1m0000gn/T/report-1778ea7a-cf43-4c76-b260-d88c7bacebcc.toml\". Submit an issue or email with the subject of \"hyperlog-tui Crash Report\" and include the report as an attachment.\n\n\nWe take privacy seriously, and do not perform any automated error collection. In order to improve the software, we rely on people to submit reports.\n\nThank you kindly!\n\u001b[0m"]
[15.682146, "o", "\u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 1)\u001b[4;5H\u001b[39m[ ] I-can-add-items\u001b[6;1Hsomething\u001b[6;11H~\u001b[6;13H(items:\u001b[6;21H2)\u001b[7;5Hi-can-do-this\u001b[7;19H~\u001b[7;21H(items:\u001b[7;29H1)\u001b[8;5Hsomething\u001b[8;15H~\u001b[8;17H(items:\u001b[8;25H3)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.020829, "o", "\u001b[1;10H~\u001b[1;12Hcreate\u001b[1;19Hitem\u001b[3;1H\u001b[38;5;8mpath: kjuulh.it-works \u001b[4;1H \u001b[5;1H\u001b[39m┌title────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\u001b[6;1H│\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[6;11H \u001b[6;13H \u001b[6;21H \u001b[6;123H│\u001b[7;1H└──────────────────────────────────────────────────────────────────────────────────────"]
[17.020983, "o", "───────────────────────────────────┘\u001b[8;1H\u001b[38;5;8m┌description──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\u001b[9;1H│ │\u001b[10;1H└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\u001b[33;5H\u001b[39mEDIT\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.678389, "o", "\u001b[6;2Ho\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.804009, "o", "\u001b[6;3Hn\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.865221, "o", "\u001b[6;4H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.982226, "o", "\u001b[6;5Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.06543, "o", "\u001b[6;6Hh\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.171003, "o", "\u001b[6;7He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.259035, "o", "\u001b[6;8H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.503667, "o", "\u001b[6;9Hb\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.613161, "o", "\u001b[6;10Ha\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.683243, "o", "\u001b[6;11Hc\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.753564, "o", "\u001b[6;12Hk\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.913502, "o", "\u001b[6;13He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.992754, "o", "\u001b[6;14Hn\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[19.124873, "o", "\u001b[6;15Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.230883, "o", "\u001b[6;16H \u001b[33;5HVIEW\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.543105, "o", "\u001b[33;1H: \u001b[33;5H \u001b[33;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.627545, "o", "\u001b[33;2Hw\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.704119, "o", "\u001b[33;3Hq\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.83678, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.837696, "o", "\u001b[1;10H \u001b[1;12H \u001b[1;19H \u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 1)\u001b[39m \u001b[4;1H [ ] I-can-add-items \u001b[5;1H \u001b[6;1Hs\u001b[6;3Hme\u001b[6;7Hing ~ (items:\u001b[6;21H2)\u001b[6;123H \u001b[7;1H i-can-do-this ~ (items: 1) \u001b[8;1H something ~ (items: 3) \u001b[9;1H \u001b[10;1H \u001b[33;1H \u001b[39"]
[20.837737, "o", "m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.838339, "o", "\u001b[33;2H--\u001b[33;5HVIEW\u001b[33;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.838812, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.85079, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.851148, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.855853, "o", "\u001b[3;20H\u001b[38;2;255;165;0m2\u001b[5;5H\u001b[39m[\u001b[5;7H]\u001b[5;9Hon-the-backend\u001b[6;1H \u001b[6;11H \u001b[6;13H \u001b[6;21H \u001b[7;1Hsomething ~ (items:\u001b[7;21H2) \u001b[7;29H \u001b[8;5Hi-can-do-this ~ (items:\u001b[8;29H1)\u001b[9;5Hsomething\u001b[9;15H~\u001b[9;17H(items:\u001b[9;25H3)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.856076, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[21.341665, "o", "\u001b[3;1Hit-works ~ (items: 2)\u001b[6;1H\u001b[38;2;255;165;0msomething ~ (items: 2)\u001b[7;1H\u001b[39m i-can-do-this ~\u001b[7;21H(items:\u001b[7;29H1)\u001b[8;5H [ ] with-items \u001b[8;29H \u001b[10;9H[\u001b[10;11H]\u001b[10;13Hand-another\u001b[11;9H\u001b[38;5;8m...\u001b[12;9H\u001b[39m[\u001b[12;11H]\u001b[12;13Hitem\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[21.516868, "o", "\u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 2)\u001b[6;1H\u001b[39m \u001b[7;1Hsomething ~ (items:\u001b[7;21H2) \u001b[7;29H \u001b[8;5Hi-can-do-this ~ (items:\u001b[8;29H1)\u001b[10;9H \u001b[10;11H \u001b[10;13H \u001b[11;9H \u001b[12;9H \u001b[12;11H \u001b[12;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[21.734986, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.153707, "o", "\u001b[1;10H~\u001b[1;12Hcreate\u001b[1;19Hitem\u001b[3;1H\u001b[38;5;8mpath: kjuulh.it-works \u001b[4;1H \u001b[5;1H\u001b[39m┌title────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\u001b[6;1H│\u001b[38;5;0m\u001b[48;5;5m \u001b[6;123H\u001b[39m\u001b[49m\u001b[7;1H└───────────────────────────────────────────────────────────────────────────────────────────────────"]
[22.153915, "o", "──────────────────────┘\u001b[8;1H\u001b[38;5;8m┌description──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\u001b[9;1H│ │\u001b[10;1H└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\u001b[33;5H\u001b[39mEDIT\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.83143, "o", "\u001b[6;2Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.930796, "o", "\u001b[6;3Ho\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.07636, "o", "\u001b[6;4Hg\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.231599, "o", "\u001b[6;5Hg\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.308648, "o", "\u001b[6;6Hl\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.420762, "o", "\u001b[6;7He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.470615, "o", "\u001b[6;8H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.62503, "o", "\u001b[6;9Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.710239, "o", "\u001b[6;10Ho\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.826044, "o", "\u001b[6;11He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.935563, "o", "\u001b[6;12Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.94551, "o", "\u001b[6;13Hn\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.15886, "o", "\u001b[6;14H'\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.231506, "o", "\u001b[6;15Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.292527, "o", "\u001b[6;16H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.45627, "o", "\u001b[6;17Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.521203, "o", "\u001b[6;18He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.689421, "o", "\u001b[6;19He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.849768, "o", "\u001b[6;20Hm\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.075116, "o", "\u001b[6;21H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.198095, "o", "\u001b[6;22Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.276978, "o", "\u001b[6;23Ho\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.372935, "o", "\u001b[6;24H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.782976, "o", "\u001b[6;24H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.934207, "o", "\u001b[6;23H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.081359, "o", "\u001b[6;22H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.223661, "o", "\u001b[6;21H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.367423, "o", "\u001b[6;20H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.515238, "o", "\u001b[6;19H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.659355, "o", "\u001b[6;18H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.803997, "o", "\u001b[6;17H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.953345, "o", "\u001b[6;16H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.095016, "o", "\u001b[6;15H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.244151, "o", "\u001b[6;14H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.393007, "o", "\u001b[6;13H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.535424, "o", "\u001b[6;12H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.683701, "o", "\u001b[6;11H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.834869, "o", "\u001b[6;10H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.979099, "o", "\u001b[6;9H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[28.205228, "o", "\u001b[6;9Hi\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[28.335979, "o", "\u001b[6;10Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[28.402599, "o", "\u001b[6;11Hn\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[28.557636, "o", "\u001b[6;12H'\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[28.65723, "o", "\u001b[6;13Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[28.724086, "o", "\u001b[6;14H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[28.89684, "o", "\u001b[6;15Hi\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[29.052154, "o", "\u001b[6;16Hm\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[29.168321, "o", "\u001b[6;17Hp\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[29.337871, "o", "\u001b[6;18Hl\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[29.444157, "o", "\u001b[6;19He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[29.519464, "o", "\u001b[6;20Hm\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[29.621293, "o", "\u001b[6;21He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[29.69862, "o", "\u001b[6;22Hn\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[29.783626, "o", "\u001b[6;23Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[29.865553, "o", "\u001b[6;24He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[30.065802, "o", "\u001b[6;25Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[30.132494, "o", "\u001b[6;26H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[30.296093, "o", "\u001b[6;27Hy\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[30.333876, "o", "\u001b[6;28He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[30.432812, "o", "\u001b[6;29Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[30.507427, "o", "\u001b[6;30H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[30.652257, "o", "\u001b[6;31Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[30.693191, "o", "\u001b[6;32Hh\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[30.845259, "o", "\u001b[6;33Ho\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[31.687752, "o", "\u001b[6;34H \u001b[33;5HVIEW\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[32.058966, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[32.119782, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[32.242868, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[33.6518, "o", "\u001b[33;1H: \u001b[33;5H \u001b[33;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[33.815445, "o", "\u001b[33;2Hw\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[33.932085, "o", "\u001b[33;3Hq\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[34.356435, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[34.357091, "o", "\u001b[1;10H \u001b[1;12H \u001b[1;19H \u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 2)\u001b[39m \u001b[4;1H [ ] I-can-add-items \u001b[5;1H [ ] on-the-backend \u001b[6;1H \u001b[6;9H \u001b[6;15H \u001b[6;27H \u001b[6;31H \u001b[6;123H \u001b[7;1Hsomething ~ (items: 2) \u001b[8;1H i-can-do-this ~ (items: 1) \u001b[9;1H something ~ (items: 3) \u001b[10;1H "]
[34.357262, "o", " \u001b[33;1H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[34.357672, "o", "\u001b[33;2H--\u001b[33;5HVIEW\u001b[33;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[34.358206, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[34.386872, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[34.387286, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[34.397925, "o", "\u001b[3;20H\u001b[38;2;255;165;0m3\u001b[6;5H\u001b[39m[\u001b[6;7H]\u001b[6;9Htoggle-isn't-implemented-yet-tho\u001b[7;1H \u001b[7;11H \u001b[7;13H \u001b[7;21H \u001b[8;1Hsomething ~ (items:\u001b[8;21H2) \u001b[8;29H \u001b[9;5Hi-can-do-this ~ (items:\u001b[9;29H1)\u001b[10;5Hsomething\u001b[10;15H~\u001b[10;17H(items:\u001b[10;25H3)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[34.398137, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[36.157515, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[36.609848, "o", "\u001b[33;1H: \u001b[33;5H \u001b[33;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[36.785846, "o", "\u001b[33;2Hq\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[40.410158, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[40.410779, "o", "\u001b[33;1H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[40.411947, "o", "\u001b[?1049l\u001b[?25h"]
[40.417589, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[40.471035, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0mis \u001b[1;38;5;208m📦 \u001b[0m\u001b[1;38;5;208mv0.1.0\u001b[0m \u001b[1;31mrs \u001b[0m\u001b[33m37s\u001b[0m \r\n\u001b[38;2;255;153;102m\u001b[0m \u001b[K"]
[40.471969, "o", "\u001b[6 q"]
[40.472835, "o", "\u001b[6 q"]
[40.473001, "o", "\u001b[?2004h"]
[41.797938, "o", "\r\r\n"]
[41.821612, "o", "\u001b[?1049h\u001b[?1000h\u001b[?1002h\u001b[?1003h\u001b[?1015h\u001b[?1006h\u001b[?2004h\u001b[>13u"]
[41.976945, "o", "\u001b[1;2H\u001b[1mAtuin v18.2.0\u001b[1;26H\u001b[22m\u001b[38;5;8m \u001b[1m<esc>\u001b[22m: exit, \u001b[1m<tab>\u001b[22m: edit, \u001b[1m<enter>\u001b[22m: run, \u001b[1m<ctrl-o>\u001b[22m: inspect history count: 23952\u001b[2;3H\u001b[1m\u001b[38;5;15m\u001b[48;5;0mSearch\u001b[2;10H\u001b[22m\u001b[39m\u001b[49m│\u001b[2;12HInspect\u001b[3;2H╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮\u001b[4;2H│\u001b[4;6H\u001b[38;5;2m0s\u001b[4;13H\u001b[38;5;4m1h ago\u001b[4;20H\u001b[39mpsql\u001b[4;25H-h\u001b[4;28Hlocalhost\u001b[4;38H-p\u001b[4;41H26257\u001b[4;47H-U\u001b[4;50Hroot\u001b[4;55H-d\u001b[4;58Hdefaultdb\u001b[4;122H│\u001b[5;2H│\u001b[5;6H\u001b[38;5;1m124ms\u001b[5;13H\u001b[38;5;4m1h ago\u001b[5;20H\u001b[39mgrpcurl\u001b[5;28H-import-path\u001b[5;41Hcrates/hyperlog-protos/proto\u001b[5;70H-proto\u001b[5;77Hhyperlog.proto\u001b[5;92H-d\u001b[5;95H'{\"root\":\u001b[5;105H\"kjuulh\",\u001b[5;115H\"path\":│\u001b[6;2H│\u001b[6;6H\u001b[38;"]
[41.977026, "o", "5;1m33ms\u001b[6;13H\u001b[38;5;4m1h ago\u001b[6;20H\u001b[39mgrpcurl\u001b[6;28H-import-path\u001b[6;41Hcrates/hyperlog-protos/proto\u001b[6;70H-proto\u001b[6;77Hhyperlog.proto\u001b[6;92H-plaintext\u001b[6;103Hlocalhost:4000\u001b[6;118Hhype│\u001b[7;2H│\u001b[7;6H\u001b[38;5;1m688ms\u001b[7;13H\u001b[38;5;4m1h ago\u001b[7;20H\u001b[39mcargo\u001b[7;26Hrun\u001b[7;30H--\u001b[7;33H--backend\u001b[7;43Hremote\u001b[7;50Hcreate-root\u001b[7;122H│\u001b[8;2H│\u001b[8;6H\u001b[38;5;2m712ms\u001b[8;13H\u001b[38;5;4m1h ago\u001b[8;20H\u001b[39mcargo\u001b[8;26Hrun\u001b[8;30H--\u001b[8;33H--backend\u001b[8;43Hremote\u001b[8;50Hcreate-root\u001b[8;62H--name\u001b[8;69Hkjuulh\u001b[8;122H│\u001b[9;2H│\u001b[9;6H\u001b[38;5;1m721ms\u001b[9;13H\u001b[38;5;4m1h ago\u001b[9;20H\u001b[39mcargo\u001b[9;26Hrun\u001b[9;30H--\u001b[9;33H--backend\u001b[9;43Hremote\u001b[9;50Hcreate-section\u001b[9;65H--name\u001b[9;72Hkjuulh\u001b[9;122H│\u001b[10;2H│\u001b[10;6H\u001b[38;5;2m684ms\u001b[10;13H\u001b[38;5;4m1h ago\u001b[10;20H\u001b[39mcargo\u001b[10;26Hrun\u001b[10;30H--\u001b[10;33H--backend\u001b[10;43Hremote\u001b[10;50H-h\u001b[10;122H│\u001b[11;2H│\u001b[11;6H\u001b[38;5;1m746ms\u001b[11;13H\u001b[38;5;4m1h ago\u001b[11;20H\u001b[39mcargo\u001b[11;26Hrun\u001b[11;30H--\u001b[11;33H--backend\u001b[11;43Hremote\u001b[11;50Hexec\u001b[11;122H│\u001b[12;2H│\u001b[12;6H\u001b[38;5;1m698ms\u001b[12;13H\u001b[38;5;4m1"]
[41.977185, "o", "h ago\u001b[12;20H\u001b[39mcargo\u001b[12;26Hrun\u001b[12;30H--\u001b[12;33H--backend\u001b[12;43Hremote\u001b[12;50Hexec\u001b[12;55Hcreate-section\u001b[12;122H│\u001b[13;2H│\u001b[13;6H\u001b[38;5;1m727ms\u001b[13;13H\u001b[38;5;4m1h ago\u001b[13;20H\u001b[39mcargo\u001b[13;26Hrun\u001b[13;30H--\u001b[13;33H--backend\u001b[13;43Hremote\u001b[13;50Hexec\u001b[13;55Hcreate-section\u001b[13;70H--root\u001b[13;77Hkjuulh\u001b[13;122H│\u001b[14;2H│\u001b[14;6H\u001b[38;5;2m685ms\u001b[14;13H\u001b[38;5;4m1h ago\u001b[14;20H\u001b[39mcargo\u001b[14;26Hrun\u001b[14;30H--\u001b[14;33H--backend\u001b[14;43Hremote\u001b[14;50Hexec\u001b[14;55Hcreate-section\u001b[14;70H--root\u001b[14;77Hkjuulh\u001b[14;84H-h\u001b[14;122H│\u001b[15;2H│\u001b[15;6H\u001b[38;5;2m688ms\u001b[15;13H\u001b[38;5;4m1h ago\u001b[15;20H\u001b[39mcargo\u001b[15;26Hrun\u001b[15;30H--\u001b[15;33H--backend\u001b[15;43Hremote\u001b[15;50Hexec\u001b[15;55Hcreate-section\u001b[15;70H--root\u001b[15;77Hkjuulh\u001b[15;84H--path\u001b[15;91Hsomething\u001b[15;122H│\u001b[16;2H│\u001b[16;6H\u001b[38;5;2m742ms\u001b[16;13H\u001b[38;5;4m1h ago\u001b[16;20H\u001b[39mcargo\u001b[16;26Hrun\u001b[16;30H--\u001b[16;33H--backend\u001b[16;43Hremote\u001b[16;50Hexec\u001b[16;55Hcreate-section\u001b[16;70H--root\u001b[16;77Hkjuulh\u001b[16;84H--path\u001b[16;91Hsomething.something\u001b[16;122H│\u001b[17;2H│\u001b[17;6H\u001b[38;5;2m1s\u001b"]
[41.97723, "o", "[17;13H\u001b[38;5;4m1h ago\u001b[17;20H\u001b[39mcargo\u001b[17;26Hrun\u001b[17;30H--\u001b[17;33H--backend\u001b[17;43Hremote\u001b[17;50Hexec\u001b[17;55H-h\u001b[17;122H│\u001b[18;2H│\u001b[18;6H\u001b[38;5;2m31ms\u001b[18;13H\u001b[38;5;4m1h ago\u001b[18;20H\u001b[39mz\u001b[18;22Hhyperlog\u001b[18;122H│\u001b[19;2H│\u001b[19;6H\u001b[38;5;2m2s\u001b[19;13H\u001b[38;5;4m1h ago\u001b[19;20H\u001b[39mrm\u001b[19;23H-rf\u001b[19;27Htarget\u001b[19;122H│\u001b[20;2H│\u001b[20;6H\u001b[38;5;1m1s\u001b[20;13H\u001b[38;5;4m1h ago\u001b[20;20H\u001b[39mcargo\u001b[20;26Hrun\u001b[20;30H--\u001b[20;33H--backend\u001b[20;43Hremote\u001b[20;122H│\u001b[21;2H│\u001b[21;4H9\u001b[21;6H\u001b[38;5;2m1s\u001b[21;13H\u001b[38;5;4m1h ago\u001b[21;20H\u001b[39mgca\u001b[21;24H\"feat:\u001b[21;31Hcan\u001b[21;35Hget\u001b[21;39Hactual\u001b[21;46Havailable\u001b[21;56Hroots\"\u001b[21;122H│\u001b[22;2H│\u001b[22;4H8\u001b[22;6H\u001b[38;5;2m2s\u001b[22;13H\u001b[38;5;4m1h ago\u001b[22;20H\u001b[39mgp\u001b[22;122H│\u001b[23;2H│\u001b[23;4H7\u001b[23;6H\u001b[38;5;2m22ms\u001b[23;13H\u001b[38;5;4m1h ago\u001b[23;20H\u001b[39mclear\u001b[23;122H│\u001b[24;2H│\u001b[24;4H6\u001b[24;6H\u001b[38;5;1m86ms\u001b[24;12H\u001b[38;5;4m57m ago\u001b[24;20H\u001b[39mgrpcurl\u001b[24;28H-import-path\u001b[24;41Hcrates/hyperlog-protos/proto\u001b[24;70H-proto\u001b[24;77Hhyperlog.proto\u001b[24;92H-d\u001b[24;95H'{\"root\":\u001b[24;105H\"kjuulh\"}'\u001b["]
[41.977287, "o", "24;116H-plain│\u001b[25;2H│\u001b[25;4H5\u001b[25;6H\u001b[38;5;2m10s\u001b[25;12H\u001b[38;5;4m16m ago\u001b[25;20H\u001b[39mhyperlog\u001b[25;122H│\u001b[26;2H│\u001b[26;4H4\u001b[26;6H\u001b[38;5;2m38s\u001b[26;13H\u001b[38;5;4m3m ago\u001b[26;20H\u001b[39mcargo\u001b[26;26Hrun\u001b[26;30H--\u001b[26;33H--backend\u001b[26;43Hremote\u001b[26;122H│\u001b[27;2H│\u001b[27;4H3\u001b[27;6H\u001b[38;5;1m49s\u001b[27;13H\u001b[38;5;4m2m ago\u001b[27;20H\u001b[39mcargo\u001b[27;26Hrun\u001b[27;30H--release\u001b[27;40H--\u001b[27;43H--remote\u001b[27;52Hbackend\u001b[27;122H│\u001b[28;2H│\u001b[28;4H2\u001b[28;6H\u001b[38;5;2m42s\u001b[28;13H\u001b[38;5;4m1m ago\u001b[28;20H\u001b[39mcargo\u001b[28;26Hrun\u001b[28;30H--release\u001b[28;40H--\u001b[28;43H--backend\u001b[28;53Hremote\u001b[28;122H│\u001b[29;2H│\u001b[29;4H1\u001b[29;6H\u001b[38;5;2m0s\u001b[29;12H\u001b[38;5;4m42s ago\u001b[29;20H\u001b[39masciinema\u001b[29;30Hrec\u001b[29;34Hdemo.cast\u001b[29;44H--overwrite\u001b[29;122H│\u001b[30;2H│\u001b[30;4H>\u001b[30;6H\u001b[38;5;2m37s\u001b[30;12H\u001b[38;5;4m39s ago\u001b[1m\u001b[38;5;1m cargo run --release -- --backend remote\u001b[30;122H\u001b[22m\u001b[39m│\u001b[31;2H│───────────────────────────────────────────────────────"]
[41.97742, "o", "────────────────────────────────────────────────────────────────│\u001b[32;2H│[\u001b[32;8HGLOBAL\u001b[32;18H]\u001b[32;122H│\u001b[33;2H╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25h\u001b[32;20H"]
[42.066499, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25h\u001b[32;20H"]
[42.319608, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25h\u001b[32;20H"]
[42.571829, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25h\u001b[32;20H"]
[42.727815, "o", "\u001b[?1000h\u001b[?1002h\u001b[?1003h\u001b[?1015h\u001b[?1006h\u001b[?1006l\u001b[?1015l\u001b[?1003l\u001b[?1002l\u001b[?1000l"]
[42.728273, "o", "\u001b[<1u\u001b[?1049l\u001b[?1006l\u001b[?1015l\u001b[?1003l\u001b[?1002l\u001b[?1000l\u001b[?2004l"]
[42.802586, "o", "\u001b[A\r\u001b[A\u001b[A\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0mis \u001b[1;38;5;208m📦 \u001b[0m\u001b[1;38;5;208mv0.1.0\u001b[0m \u001b[1;31mrs \u001b[0m\u001b[33m37s\u001b[0m \r\n\u001b[38;2;255;153;102m\u001b[0m \u001b[K"]
[42.803675, "o", "cargo run --release -- --backend remote"]
[42.809234, "o", "\u001b[39D\u001b[32mc\u001b[32ma\u001b[32mr\u001b[32mg\u001b[32mo\u001b[39m\u001b[34C"]
[42.809682, "o", "\u001b[?1l\u001b>"]
[42.809828, "o", "\u001b[?2004l"]
[42.814158, "o", "\u001b[0 q"]
[42.814414, "o", "\r\r\n"]
[43.125266, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `GraphItem`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_item.rs:1:26\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse hyperlog_core::log::{GraphItem, ItemState};\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_imports)]` on by default\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `hyperlog_core::log::GraphItem`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_section.rs:1:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse hyperlog_core::log::GraphItem;\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused i"]
[43.125298, "o", "mports: `GraphItem`, `ItemState`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_available_roots.rs:1:26\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse hyperlog_core::log::{GraphItem, ItemState};\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\r\n"]
[43.125414, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `sqlx::types::Json`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_available_roots.rs:2:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m2\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse sqlx::types::Json;\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `root`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:102:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m102\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m root,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `root: _`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_variables)]` on by default\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variab"]
[43.125455, "o", "le: `path`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:103:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m103\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m path,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `path: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `title`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:104:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m104\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m title,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `title: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `description`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:105:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;"]
[43.125526, "o", "12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m105\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m description,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `description: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `state`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:106:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m106\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m state,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `state: _`\u001b[0m\r\n\r\n"]
[43.125576, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `root`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:108:35\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m108\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::ToggleItem { root, path } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `root: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `path`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:108:41\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m108\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::ToggleItem { root, path } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `path: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b["]
[43.125659, "o", "1m: unused variable: `root`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:109:29\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m109\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::Move { root, src, dest } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `root: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `src`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:109:35\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m109\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::Move { root, src, dest } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `src: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `dest`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0"]
[43.125748, "o", "m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:109:40\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m109\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::Move { root, src, dest } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `dest: _`\u001b[0m\r\n\r\n"]
[43.125824, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `req`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_available_roots.rs:26:33\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m26\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_req`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `root_name` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_item.rs:31:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m29\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Root {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m----\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m30\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12"]
[43.125897, "o", "m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m31\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m root_name: String,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(dead_code)]` on by default\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `id` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_item.rs:36:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m35\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Section {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m-------\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m36\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `root_name` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m"]
[43.126022, "o", "\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_section.rs:19:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m17\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Root {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m----\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m18\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m19\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m root_name: String,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `id` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_graph.rs:37:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m36\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Node {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m----\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0"]
[43.126115, "o", "m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m37\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `Node` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: multiple associated items are never used\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_graph.rs:152:16\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m151\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m impl Engine {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m-----------\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12massociated items in this implementation\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m152\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn engine_from_str(input: &str) -> anyhow::Result<Self> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m "]
[43.126211, "o", " \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m158\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn to_str(&self) -> anyhow::Result<String> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m219\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn get_mut(&mut self, root: &str, path: &[&str]) -> Option<&mut GraphItem> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m225\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn take(&mut self, root: &str, path: &[&str]) -> Option<GraphItem> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m231\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn section_move(\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b"]
[43.126253, "o", "[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m268\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn delete(&mut self, root: &str, path: &[&str]) -> anyhow::Result<()> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m274\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn toggle_item(&mut self, root: &str, path: &[&str]) -> anyhow::Result<()> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m290\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn update_item(\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m341\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn get_roots(&self) -> Option<Vec<String>> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0"]
[43.126325, "o", "m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\r\n"]
[43.126992, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `std::net::SocketAddr`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog/src/cli.rs:1:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse std::net::SocketAddr;\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_imports)]` on by default\u001b[0m\r\n\r\n"]
[43.127883, "o", "\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m:\u001b[0m `hyperlog-server` (lib) generated 20 warnings (run `cargo fix --lib -p hyperlog-server` to apply 4 suggestions)\r\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m:\u001b[0m `hyperlog` (bin \"hyperlog\") generated 1 warning (run `cargo fix --bin \"hyperlog\"` to apply 1 suggestion)\r\n"]
[43.1279, "o", "\u001b[1m\u001b[32m Finished\u001b[0m `release` profile [optimized] target(s) in 0.29s\r\n"]
[43.137727, "o", "\u001b[1m\u001b[32m Running\u001b[0m `target/release/hyperlog --backend remote`\r\n"]
[43.314772, "o", "\u001b[?1049h"]
[43.323208, "o", "\u001b[1;1H\u001b[38;5;2mhyperlog\u001b[2;1H\u001b[39m───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[3;1Hit-works\u001b[3;10H~\u001b[3;12H(items:\u001b[3;20H3)\u001b[4;5H[\u001b[4;7H]\u001b[4;9HI-can-add-items\u001b[5;5H\u001b[38;5;8m...\u001b[6;5H\u001b[39m[\u001b[6;7H]\u001b[6;9Htoggle-isn't-implemented-yet-tho\u001b[8;1Hsomething\u001b[8;11H~\u001b[8;13H(items:\u001b[8;21H2)\u001b[9;5Hi-can-do-this\u001b[9;19H~\u001b[9;21H(items:\u001b[9;29H1)\u001b[10;5Hsomething\u001b[10;15H~\u001b[10;17H(items:\u001b[10;25H3)\u001b[33;2H--\u001b[33;5HVIEW\u001b[33;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[43.929581, "o", "\u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 3)\u001b[5;5H\u001b[39m[ ]\u001b[5;9Hon-the-backend\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[44.075108, "o", "\u001b[3;1Hit-works ~ (items: 3)\u001b[4;5H\u001b[38;2;255;165;0m[ ] I-can-add-items\u001b[8;1H\u001b[39m \u001b[8;11H \u001b[8;13H \u001b[8;21H \u001b[9;5H \u001b[9;19H \u001b[9;21H \u001b[9;29H \u001b[10;5H \u001b[10;15H \u001b[10;17H \u001b[10;25H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[44.874727, "o", "\u001b[4;5H[ ] I-can-add-items\u001b[5;5H\u001b[38;2;255;165;0m[ ] on-the-backend\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[45.061332, "o", "\u001b[5;5H[ ] on-the-backend\u001b[6;5H\u001b[38;2;255;165;0m[ ] toggle-isn't-implemented-yet-tho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[45.206712, "o", "\u001b[5;5H\u001b[38;2;255;165;0m[ ] on-the-backend\u001b[6;5H\u001b[39m[ ] toggle-isn't-implemented-yet-tho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[45.37008, "o", "\u001b[4;5H\u001b[38;2;255;165;0m[ ] I-can-add-items\u001b[5;5H\u001b[39m[ ] on-the-backend\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[45.673509, "o", "\u001b[4;5H[ ] I-can-add-items\u001b[5;5H\u001b[38;2;255;165;0m[ ] on-the-backend\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[46.007542, "o", "\u001b[4;5H\u001b[38;2;255;165;0m[ ] I-can-add-items\u001b[5;5H\u001b[39m[ ] on-the-backend\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[46.189383, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[46.345585, "o", "\u001b[4;5H[ ] I-can-add-items\u001b[5;5H\u001b[38;2;255;165;0m[ ] on-the-backend\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[46.567759, "o", "\u001b[5;5H[ ] on-the-backend\u001b[6;5H\u001b[38;2;255;165;0m[ ] toggle-isn't-implemented-yet-tho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[46.698547, "o", "\u001b[5;5H\u001b[38;2;255;165;0m[ ] on-the-backend\u001b[6;5H\u001b[39m[ ] toggle-isn't-implemented-yet-tho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[47.043763, "o", "\u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 3)\u001b[5;5H\u001b[39m[ ] on-the-backend\u001b[8;1Hsomething\u001b[8;11H~\u001b[8;13H(items:\u001b[8;21H2)\u001b[9;5Hi-can-do-this\u001b[9;19H~\u001b[9;21H(items:\u001b[9;29H1)\u001b[10;5Hsomething\u001b[10;15H~\u001b[10;17H(items:\u001b[10;25H3)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[47.720694, "o", "\u001b[3;1Hit-works ~ (items: 3)\u001b[5;5H\u001b[38;5;8m...\u001b[5;9H\u001b[39m \u001b[8;1H\u001b[38;2;255;165;0msomething ~ (items: 2)\u001b[10;5H\u001b[39m [ ] with-\u001b[10;23H \u001b[10;25H \u001b[11;5Hsomething\u001b[11;15H~\u001b[11;17H(items:\u001b[11;25H3)\u001b[12;9H[\u001b[12;11H]\u001b[12;13Hand-another\u001b[13;9H\u001b[38;5;8m...\u001b[14;9H\u001b[39m[\u001b[14;11H]\u001b[14;13Hitem\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[47.90605, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[48.194979, "o", "\u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 3)\u001b[5;5H\u001b[39m[ ]\u001b[5;9Hon-the-backend\u001b[8;1Hsomething ~ (items: 2)\u001b[10;5Hsomething ~ (\u001b[10;23H:\u001b[10;25H3)\u001b[11;5H \u001b[11;15H \u001b[11;17H \u001b[11;25H \u001b[12;9H \u001b[12;11H \u001b[12;13H \u001b[13;9H \u001b[14;9H \u001b[14;11H \u001b[14;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[48.367241, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[49.205066, "o", "\u001b[33;1H: \u001b[33;5H \u001b[33;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[49.360314, "o", "\u001b[33;2Hq\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[49.565543, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[49.567508, "o", "\u001b[33;1H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[49.568849, "o", "\u001b[?1049l\u001b[?25h"]
[49.570699, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[49.627447, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0mis \u001b[1;38;5;208m📦 \u001b[0m\u001b[1;38;5;208mv0.1.0\u001b[0m \u001b[1;31mrs \u001b[0m\u001b[33m6s\u001b[0m \r\n\u001b[38;2;255;153;102m\u001b[0m \u001b[K"]
[49.628604, "o", "\u001b[6 q"]
[49.629752, "o", "\u001b[6 q"]
[49.62996, "o", "\u001b[?2004h"]
[51.218262, "o", "\u001b[2 q"]
[51.265765, "o", "\r\r\u001b[A\u001b[A\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0mis \u001b[1;38;5;208m📦 \u001b[0m\u001b[1;38;5;208mv0.1.0\u001b[0m \u001b[1;31mrs \u001b[0m\u001b[33m6s\u001b[0m \r\n\u001b[32m\u001b[0m \u001b[K"]
[52.056217, "o", "\u001b[6 q"]
[52.095427, "o", "\r\r\u001b[A\u001b[A\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0mis \u001b[1;38;5;208m📦 \u001b[0m\u001b[1;38;5;208mv0.1.0\u001b[0m \u001b[1;31mrs \u001b[0m\u001b[33m6s\u001b[0m \r\n\u001b[38;2;255;153;102m\u001b[0m \u001b[K"]
[52.478423, "o", "\u001b[?2004l\r\r\n"]

15
scripts/dev.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env zsh
echo "starting services"
docker compose -f templates/docker-compose.yaml up -d --remove-orphans
sleep 5
tear_down() {
echo "cleaning up services in the background"
(docker compose -f templates/docker-compose.yaml down -v &) > /dev/null 2>&1
}
trap tear_down SIGINT
RUST_LOG=info,hyperlog=trace cargo watch -x 'run -F include_server -- serve'

5
scripts/install.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env zsh
set -eo pipefail
cargo install --path crates/hyperlog --force

View File

@@ -1,4 +1,3 @@
version: "3"
services:
crdb:
restart: 'always'
@@ -11,5 +10,5 @@ services:
retries: 5
start_period: '20s'
ports:
- 8080:8080
- 28080:8080
- '26257:26257'