handle multiple or no pijul identity

tzemanovic
Mar 6, 2026, 4:09 PM
E7HE2UFTIOINUJQBRVCIG5GMANK6XWOYFX2ZHA73QJ6RW627G55QC

Dependencies

  • [2] EC3TVL4X add untracked files
  • [3] 2VUX5BTD load identity
  • [4] A5YBC77V record!
  • [5] 4WO3ZJM2 show untracked files' contents
  • [6] 23SFYK4Q big view refactor into a new crate
  • [7] 7SSBM4UQ view: refactor repo view
  • [8] JZXYSIYD channel selection!
  • [9] WAOGSCOJ very nice refactor, wip adding channels logs
  • [10] EJPSD5XO shared allowed actions conditions between update and view
  • [11] YK3MOJJL chonky refactor, wip other channels logs & diffs
  • [12] 7WCB5YQJ refactor msgs and modules
  • [13] AZ5D2LQU allow to set record description
  • [14] PKLUHYE4 allow to copy change hash
  • [15] KF2LDB5Y handle repo init errors
  • [16] LFEMJYYD start of to_record selection
  • [17] UTDTZCTX pull+push status, add info reports
  • [18] OLT666N4 fix screenshot test to include status, fix failed test report
  • [19] TEDT26JQ add push and pull sub-menus
  • [20] YRGDFHAB project dir picker
  • [21] LPSUBGUB add projects picker
  • [22] 5BAPU7K6 dir picker key navigation
  • [23] T4UECD3S picking projects key nav and scrollable
  • [24] IOXNOVX2 allow to initiate a new repo
  • [25] 6E6MSENZ allow to add untracked files recursively
  • [26] ACDXXAX2 refactor main's updates into smaller fns
  • [27] YKHE3XMW refactor diffs handling
  • [28] 3SYSJKYL add app icon
  • [29] GOLHUD6R nav-scrollable: set skip-able sections
  • [30] ELG3UDT6 allow to rm added files
  • [31] ZVI4AWER woot contents_diff
  • [32] MORKDJUE use allowed actions binding for key subs
  • [33] UB2ITZJS refresh changed files on FS changes
  • [34] ONRCENKT rm unnecessary state from repo's internal state
  • [35] CFYW3HGZ wip: display changed files
  • [36] V55EAIWQ add src file LRU cache
  • [37] NRCUG4R2 load changed files src when selected
  • [38] PTFDJ567 add untracked files encoding
  • [39] AHWWRC73 navigate log entries
  • [40] L6KSEFQI move cursor related stuff into its module
  • [41] HC7ROIBC move main diffs state out of cursor
  • [42] UR4J677R nav for log changes and refactors
  • [43] KM5PSZ4A watch repo once loaded
  • [44] FVA36HBV restart repo manager task if it crashes
  • [45] CULHFNIV add error report view
  • [46] AMPZ2BXK show changed files diffs (only Edit atm)
  • [47] I2AG42PA new cols layout
  • [48] SASAN2XC use nav-scrollable
  • [49] W7IUT3ZV start recording impl
  • [50] UJPRF6DA fix log changes selection
  • [51] KWTBNTO3 diffs selection and scrolling
  • [52] FR52XEMW add action for log change file diff
  • [53] TEI5NQ3S add log files selection
  • [54] OQ6HSAWH show record log
  • [55] VCNKFNUF app init test
  • [56] W4LFX7IH group diffs by file name
  • [57] SWWE2R6M display basic repo stuff
  • [58] YBJRDOTC make all repo actions async
  • [59] QMAUTRB6 refactor diff
  • [60] WXQBBQ2A update nightly
  • [61] JE44NYHM display log files diffs
  • [62] IQDCHWCP load a pijul repo
  • [63] Z2CJPWZE focus record message text_editor on spawn
  • [64] KT5UYXGK fix selection after adding file, add changed file diffs
  • [65] SEJXDXPW improve file watcher to respect .ignore file
  • [66] BFN2VHZS refactor file stuff into sub-mod
  • [67] S2NVIFXR allow to enter record msg
  • [68] BJXUYQ2Y show untracked file contents in read-only text editor
  • [69] 4ELJZGRJ load and store all change diffs at once
  • [70] C3OS2JJ6 clear cache on saving record & refresh
  • [71] PTWZYQFR use nav-scrollable for repo status
  • [72] YGZ3VCW4 add push
  • [73] OLDN7R34 retry upsert project
  • [74] 6YZAVBWU Initial commit
  • [75] WT3GA27P add cursor with selection
  • [76] Y5ATDI2H convert changed file diffs and load src only if any needs it
  • [77] MJDGPSHG WIP contents diff
  • [78] OPXFZKEB view tests setup
  • [79] D7A7MSIH allow to defer or abandon record, add buttons

Change contents

  • replacement in libflorescence/src/identity.rs at line 8
    [3.100][3.100:249]()
    pub fn load() -> Id {
    let all = Id::load_all().unwrap();
    // TODO: handle identity creation if none found
    all.into_iter().next().unwrap()
    [3.100]
    [3.249]
    pub fn load() -> anyhow::Result<Vec<Id>> {
    Id::load_all()
  • edit in inflorescence_view/src/view.rs at line 13
    [16.266]
    [6.12583]
    use libflorescence::identity::Id;
  • edit in inflorescence_view/src/view.rs at line 45
    [21.13266]
    [10.404]
    SelectIdentity(usize),
  • replacement in inflorescence_view/src/view.rs at line 229
    [15.2045][20.20382:20426](),[20.20426][9.4062:4156](),[9.4062][9.4062:4156]()
    ManagingRepoSubState::SelectingId {
    user_ids: _,
    user_selection_ix: _,
    user_selection_nav: _,
    [15.2045]
    [9.4156]
    ManagingRepoSubState::SelectingIdentity {
    ids,
    selection_ix,
    selection_nav,
    confirmed_selection_ix,
  • replacement in inflorescence_view/src/view.rs at line 235
    [9.4177][9.4177:4199]()
    } => todo!(),
    [9.4177]
    [20.20427]
    } => {
    let main = el(container(view_selecting_identity(
    ids,
    confirmed_selection_ix.unwrap_or_else(|| *selection_ix),
    selection_nav,
    ))
    .width(Length::Fill)
    .height(Length::Fill));
    add_actions_and_report(
    None,
    main,
    window_size,
    allowed_actions,
    report,
    )
    }
  • edit in inflorescence_view/src/view.rs at line 260
    [10.664]
    [7.124]
    ManagingRepoSubState::NoIdFound { repo: _ } => {
    let main = el(container(text("No Pijul identity found. Create a new one in with Pijul CLI and then reload it here."))
    .width(Length::Fill)
    .height(Length::Fill));
    add_actions_and_report(
    None,
    main,
    window_size,
    allowed_actions,
    report,
    )
    }
  • edit in inflorescence_view/src/view.rs at line 280
    [8.3730]
    [7.255]
    }
    fn view_selecting_identity<'a>(
    ids: &'a [Id],
    selection_ix: usize,
    selection_nav: &'a nav_scrollable::State,
    ) -> Element<'a, Msg, Theme> {
    el(column([
    el(text("Select identity:")),
    el(nav_scrollable(
    selection_nav,
    ids.iter().enumerate().map(|(ix, id)| {
    el(button(text(id.name.to_string()))
    .on_press(Msg::SelectIdentity(ix))
    .class(if ix == selection_ix {
    theme::Button::Selected
    } else {
    theme::Button::Normal
    }))
    }),
    )),
    ]))
  • replacement in inflorescence_view/src/view/test.rs at line 50
    [20.21578][18.106:132](),[18.106][18.106:132]()
    user_ids: vec![],
    [20.21578]
    [18.132]
    user_ids: None,
  • replacement in inflorescence_model/src/model.rs at line 99
    [12.4561][12.4561:4588]()
    user_ids: Vec<Id>,
    [12.4561]
    [12.4588]
    user_ids: Option<Vec<Id>>,
    repo: Option<repo::State>,
    },
    SelectingIdentity {
    ids: Vec<Id>,
    selection_ix: usize,
    selection_nav: nav_scrollable::State,
    // This will be set if confirmed before a repo is loaded
    confirmed_selection_ix: Option<usize>,
  • replacement in inflorescence_model/src/model.rs at line 110
    [12.4630][12.4630:4760]()
    SelectingId {
    user_ids: Vec<Id>,
    user_selection_ix: usize,
    user_selection_nav: nav_scrollable::State,
    [12.4630]
    [12.4760]
    NoIdFound {
  • edit in inflorescence_model/src/action.rs at line 58
    [19.532010]
    [10.4320]
    ReloadIdentity,
  • edit in inflorescence_model/src/action.rs at line 121
    [19.532079]
    [10.5127]
    (ReloadIdentity, ReloadIdentity) => true,
  • edit in inflorescence_model/src/action.rs at line 143
    [19.532119]
    [10.5587]
    (ReloadIdentity, _) => false,
  • replacement in inflorescence_model/src/action.rs at line 460
    [20.3593292][20.3593292:3593360]()
    model::ManagingRepoSubState::SelectingId { .. } => todo!(),
    [20.3593292]
    [20.3593360]
    model::ManagingRepoSubState::SelectingIdentity { .. } => {
    vec![confirm("confirm"), down(), up()]
    }
  • edit in inflorescence_model/src/action.rs at line 466
    [20.3593474]
    [20.3593474]
    model::ManagingRepoSubState::NoIdFound { .. } => vec![Binding {
    keys_str: "r",
    keys: ModKeys::One(ModKey {
    key: Key::Character("r".into()),
    mods: Mods::NONE,
    }),
    label: "Reload identity",
    msg: Some(FilteredMsg::ReloadIdentity),
    }],
  • replacement in inflorescence/src/test.rs at line 58
    [20.3594608][20.3594608:3594686]()
    && matches!(msg, Msg::ManagingRepo(ManagingRepoMsg::LoadedId(_)))
    [20.3594608]
    [20.3594686]
    && matches!(
    msg,
    Msg::ManagingRepo(ManagingRepoMsg::LoadedIdentities(_))
    )
  • replacement in inflorescence/src/main.rs at line 112
    [20.3609729][14.2018:2041](),[14.2018][14.2018:2041]()
    LoadedId(Box<Id>),
    [20.3609729]
    [14.2061]
    LoadedIdentities(anyhow::Result<Vec<Id>>),
  • replacement in inflorescence/src/main.rs at line 221
    [2.1997][3.531:639](),[3.639][20.3611492:3611540](),[4.4572][3.669:677](),[6.25685][3.669:677](),[20.3611540][3.669:677](),[3.669][3.669:677]()
    let load_id_task = Task::future(async {
    let id = spawn_blocking(identity::load).await.unwrap();
    ManagingRepoMsg::LoadedId(Box::new(id))
    });
    [2.1997]
    [5.2126]
    let load_ids_task = load_identities_task();
  • replacement in inflorescence/src/main.rs at line 227
    [20.3611583][20.3611583:3611628]()
    load_id_task.map(Msg::ManagingRepo),
    [20.3611583]
    [20.3611628]
    load_ids_task.map(Msg::ManagingRepo),
  • replacement in inflorescence/src/main.rs at line 243
    [20.3611788][10.23669:23703](),[10.23669][10.23669:23703]()
    user_ids: Vec::new(),
    [20.3611788]
    [10.23703]
    user_ids: None,
  • replacement in inflorescence/src/main.rs at line 469
    [23.4852][23.4852:4920]()
    | action::FilteredMsg::EnterSubMenu(_) => Task::none(),
    [23.4852]
    [23.4920]
    | action::FilteredMsg::EnterSubMenu(_)
    | action::FilteredMsg::ReloadIdentity => Task::none(),
  • replacement in inflorescence/src/main.rs at line 489
    [21.21457][21.21457:21507]()
    | view::Msg::ToRecord(_) => Task::none(),
    [21.21457]
    [21.21507]
    | view::Msg::ToRecord(_)
    | view::Msg::SelectIdentity(_) => Task::none(),
  • replacement in inflorescence/src/main.rs at line 573
    [21.21660][20.3613691:3613846](),[20.3613691][20.3613691:3613846](),[10.23975][9.80169:80301](),[20.3613846][9.80169:80301](),[9.80169][9.80169:80301]()
    ManagingRepoMsg::LoadedId(id) => {
    match &mut model.sub {
    model::ManagingRepoSubState::Loading { user_ids, repo } => {
    // TODO switch to `SelectingId` if more than one id found
    if let Some(repo) = repo.take() {
    [21.21660]
    [20.3613847]
    ManagingRepoMsg::LoadedIdentities(ids) => {
    match ids {
    Ok(mut user_ids) => {
    let get_repo = |sub: &mut model::ManagingRepoSubState| {
    match sub {
    model::ManagingRepoSubState::Loading {
    repo,
    ..
    } => repo.take(),
    model::ManagingRepoSubState::SelectingIdentity {
    repo,
    ..
    } => repo.take(),
    model::ManagingRepoSubState::NoIdFound { repo } => {
    repo.take()
    }
    model::ManagingRepoSubState::Ready(_) => {
    unreachable!()
    }
    }
    };
    if user_ids.is_empty() {
    let repo = get_repo(&mut model.sub);
  • replacement in inflorescence/src/main.rs at line 597
    [20.3613883][20.3613883:3614005]()
    model::ManagingRepoSubState::Ready(ReadyState {
    user_id: *id,
    [20.3613883]
    [20.3614005]
    model::ManagingRepoSubState::NoIdFound { repo }
    } else if user_ids.len() == 1 {
    let user_id = user_ids.pop().unwrap();
    match &mut model.sub {
    model::ManagingRepoSubState::Loading {
    user_ids,
  • replacement in inflorescence/src/main.rs at line 604
    [20.3614043][20.3614043:3614459]()
    selection: default(),
    navigation: default(),
    record_changes: default(),
    forking_channel_name: default(),
    logs: default(),
    to_record: default(),
    jobs: default(),
    })
    [20.3614043]
    [9.80732]
    } => {
    if let Some(repo) = repo.take() {
    model.sub =
    model::ManagingRepoSubState::Ready(
    ReadyState {
    user_id,
    repo,
    selection: default(),
    navigation: default(),
    record_changes: default(),
    forking_channel_name: default(),
    logs: default(),
    to_record: default(),
    jobs: default(),
    },
    )
    } else {
    *user_ids = Some(vec![user_id]);
    }
    }
    model::ManagingRepoSubState::SelectingIdentity {
    ..
    } => {
    unreachable!()
    }
    model::ManagingRepoSubState::Ready(..) => {
    unreachable!()
    }
    model::ManagingRepoSubState::NoIdFound {
    repo,
    } => {
    if let Some(repo) = repo.take() {
    model.sub =
    model::ManagingRepoSubState::Ready(
    ReadyState {
    user_id,
    repo,
    selection: default(),
    navigation: default(),
    record_changes: default(),
    forking_channel_name: default(),
    logs: default(),
    to_record: default(),
    jobs: default(),
    },
    )
    } else {
    model.sub = model::ManagingRepoSubState::SelectingIdentity {
    ids: user_ids,
    selection_ix: default(),
    selection_nav: default(),
    confirmed_selection_ix: default(),
    repo: default(),
    };
    }
    }
    }
  • replacement in inflorescence/src/main.rs at line 662
    [9.80761][9.80761:80805]()
    user_ids.push(*id);
    [9.80761]
    [9.80805]
    let repo = get_repo(&mut model.sub);
    model.sub =
    model::ManagingRepoSubState::SelectingIdentity {
    ids: user_ids,
    selection_ix: default(),
    selection_nav: default(),
    confirmed_selection_ix: default(),
    repo,
    }
  • replacement in inflorescence/src/main.rs at line 673
    [9.80845][20.3614460:3614564](),[20.3614564][20.3614564:3614582](),[20.3614582][20.3614582:3614642](),[10.24172][9.80952:80987](),[20.3614642][9.80952:80987](),[9.80952][9.80952:80987]()
    model::ManagingRepoSubState::SelectingId { .. } => {
    unreachable!()
    }
    model::ManagingRepoSubState::Ready(..) => {
    unreachable!()
    [9.80845]
    [9.80987]
    Err(err) => {
    let msg =
    format!("Failed to load Pijul identity with {err:#?}");
    report::show_err(report, msg);
  • replacement in inflorescence/src/main.rs at line 993
    [22.19039][22.19039:19107]()
    | action::FilteredMsg::EnterSubMenu(_) => Task::none(),
    [22.19039]
    [22.19107]
    | action::FilteredMsg::EnterSubMenu(_)
    | action::FilteredMsg::ReloadIdentity => Task::none(),
  • replacement in inflorescence/src/main.rs at line 1009
    [21.23235][21.23235:23288]()
    | view::Msg::PickNewProject => Task::none(),
    [21.23235]
    [20.3616655]
    | view::Msg::PickNewProject
    | view::Msg::SelectIdentity(_) => Task::none(),
  • edit in inflorescence/src/main.rs at line 1095
    [16.17224]
    [21.23289]
    view::Msg::SelectIdentity(ix) => {
    if let model::ManagingRepoSubState::SelectingIdentity {
    ids,
    repo,
    confirmed_selection_ix,
    ..
    } = &mut model.sub
    {
    if let Some(repo) = repo.take() {
    let user_id = ids.get(ix).unwrap().clone();
    model.sub =
    model::ManagingRepoSubState::Ready(ReadyState {
    user_id,
    repo,
    selection: default(),
    navigation: default(),
    record_changes: default(),
    forking_channel_name: default(),
    logs: default(),
    to_record: default(),
    jobs: default(),
    });
    } else {
    *confirmed_selection_ix = Some(ix);
    }
    }
    Task::none()
    }
  • edit in inflorescence/src/main.rs at line 1197
    [24.7983]
    [24.7983]
    } else if let model::ManagingRepoSubState::SelectingIdentity {
    ids,
    selection_ix,
    selection_nav: _,
    confirmed_selection_ix,
    repo,
    } = &mut model.sub
    {
    if let Some(repo) = repo.take() {
    let user_id = ids.get(*selection_ix).unwrap().clone();
    model.sub =
    model::ManagingRepoSubState::Ready(ReadyState {
    user_id,
    repo,
    selection: default(),
    navigation: default(),
    record_changes: default(),
    forking_channel_name: default(),
    logs: default(),
    to_record: default(),
    jobs: default(),
    });
    } else {
    *confirmed_selection_ix = Some(*selection_ix);
    }
    Task::none()
  • edit in inflorescence/src/main.rs at line 1278
    [24.8094]
    [11.60508]
    } else if let model::ManagingRepoSubState::SelectingIdentity {
    ids,
    selection_ix,
    selection_nav: _,
    confirmed_selection_ix,
    repo: _,
    } = &mut model.sub
    {
    match msg {
    selection::Msg::PressDir(dir) => match dir {
    inflorescence_model::selection::Dir::Down => {
    *confirmed_selection_ix = None;
    if *selection_ix == ids.len().saturating_sub(1) {
    *selection_ix = 0;
    } else {
    *selection_ix += 1;
    }
    }
    inflorescence_model::selection::Dir::Up => {
    *confirmed_selection_ix = None;
    if *selection_ix == 0 {
    *selection_ix = ids.len().saturating_sub(1);
    } else {
    *selection_ix -= 1;
    }
    }
    inflorescence_model::selection::Dir::Right
    | inflorescence_model::selection::Dir::Left => {}
    },
    selection::Msg::AltPressDir(_) => {}
    }
    Task::none()
  • edit in inflorescence/src/main.rs at line 1437
    [16.18195]
    [11.60824]
    action::FilteredMsg::ReloadIdentity => {
    load_identities_task().map(Msg::ManagingRepo)
    }
  • edit in inflorescence/src/main.rs at line 1554
    [11.64717]
    [25.3489]
    }
    fn load_identities_task() -> Task<ManagingRepoMsg> {
    Task::future(async {
    let ids = spawn_blocking(identity::load).await.unwrap();
    ManagingRepoMsg::LoadedIdentities(ids)
    })
  • replacement in inflorescence/src/main.rs at line 2155
    [20.3623971][20.3623971:3624020](),[13.14403][9.90921:90976](),[20.3624020][9.90921:90976](),[9.90921][9.90921:90976](),[9.90976][20.3624021:3624085](),[10.26488][9.91022:91174](),[20.3624085][9.91022:91174](),[9.91022][9.91022:91174](),[9.91174][13.14404:14451](),[13.14451][9.91217:91307](),[9.91217][9.91217:91307](),[9.91307][16.18196:18238](),[16.18238][17.9125:9162](),[17.9162][9.91307:91347](),[16.18238][9.91307:91347](),[9.91307][9.91307:91347](),[9.91347][20.3624086:3624145](),[10.26536][9.91388:91584](),[20.3624145][9.91388:91584](),[9.91388][9.91388:91584]()
    model.sub = if user_ids.len() == 1 {
    let user_id = user_ids.pop().unwrap();
    model::ManagingRepoSubState::Ready(ReadyState {
    user_id,
    repo: repo_state,
    selection: default(),
    navigation: default(),
    record_changes: default(),
    forking_channel_name: default(),
    logs: default(),
    to_record: default(),
    jobs: default(),
    })
    } else {
    model::ManagingRepoSubState::SelectingId {
    user_ids: mem::take(user_ids),
    user_selection_ix: default(),
    user_selection_nav: default(),
    repo: Some(repo_state),
    [20.3623971]
    [9.91584]
    if let Some(user_ids) = user_ids {
    model.sub = if user_ids.len() == 1 {
    let user_id = user_ids.pop().unwrap();
    model::ManagingRepoSubState::Ready(ReadyState {
    user_id,
    repo: repo_state,
    selection: default(),
    navigation: default(),
    record_changes: default(),
    forking_channel_name: default(),
    logs: default(),
    to_record: default(),
    jobs: default(),
    })
    } else {
    model::ManagingRepoSubState::SelectingIdentity {
    ids: mem::take(user_ids),
    selection_ix: default(),
    selection_nav: default(),
    confirmed_selection_ix: default(),
    repo: Some(repo_state),
    }
  • replacement in inflorescence/src/main.rs at line 2180
    [20.3624156][20.3624156:3624223]()
    model::ManagingRepoSubState::SelectingId { repo, .. } => {
    [20.3624156]
    [20.3624223]
    model::ManagingRepoSubState::SelectingIdentity { repo, .. } => {
  • edit in inflorescence/src/main.rs at line 2184
    [20.3624313]
    [9.91733]
    model::ManagingRepoSubState::NoIdFound { repo } => {
    *repo = Some(repo_state)
    }
  • replacement in inflorescence/src/main.rs at line 2269
    [10.26831][20.3624772:3624839]()
    model::ManagingRepoSubState::SelectingId { repo, .. } => {
    [10.26831]
    [20.3624839]
    model::ManagingRepoSubState::SelectingIdentity { repo, .. } => {
  • edit in inflorescence/src/main.rs at line 2295
    [9.92659]
    [9.92659]
    }
    model::ManagingRepoSubState::NoIdFound { repo } => {
    *repo = Some(repo_state)