slipcase-open 0.1.5

Open the payload of a Slipcase container in its own application, and write edits back into the container
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
//! The command line over the engine.
//
// Author: David M. Anderson
// Built with AI assistance (Claude, Anthropic)
//
//! Concept 9 makes this the floor: always present, the way the session list
//! stays reachable on a desktop that has no tray, and the harness the engine is
//! driven by before the notifications and the tray exist.
//!
//! **Every verb is a client of the front door first.** Concept 8: where an
//! instance is running the invocation hands over and exits; where none is, an
//! `open` becomes the instance and the rest do their work against the state
//! directory, which is where the answer lives when nobody is holding anything.
//!
//! **The instance runs in the foreground and holds the terminal.** Detaching
//! means `fork`, and this crate forbids `unsafe`; nothing in concept 8 asks for
//! a background process, and from Phase 3 the tool is started from a desktop
//! entry rather than a shell, where there is no terminal to hold. Interrupting
//! it rather than closing its sessions leaves them recoverable, which is the
//! crash path working as designed.

// The level `Cargo.toml` explains: `forbid` everywhere Windows is not.
#![cfg_attr(not(windows), forbid(unsafe_code))]
// No console of its own, and this is behaviour rather than appearance.
//
// A console subsystem binary always has a terminal, so `is_terminal` below was
// answering yes to a double-click and the voice was the client's — which is the
// branch where the instance says nothing through concept 9's channel, because a
// person is taken to be reading the lines it hands back. Measured on 2026-09-01
// against the packaged build: the narration appeared in a console window that
// stayed open for the life of the session, and no toast was ever raised.
//
// So the subsystem is what makes the channel fire on a double-click, and the
// console window it also removes is the smaller half of it. `attach_console`
// gives the terminal back to an invocation that came from one.
#![cfg_attr(windows, windows_subsystem = "windows")]

use std::io::IsTerminal as _;
use std::path::{Path, PathBuf};
use std::process::ExitCode;

use clap::{Args, CommandFactory as _, Parser, Subcommand};

use slipcase_open::endpoint;
use slipcase_open::ipc::{self, Request, Response, Voice};
use slipcase_open::outside::Outside;
use slipcase_open::platform::Host;
use slipcase_open::policy;
use slipcase_open::present::{self, Channel, Report};
use slipcase_open::resident::{self, Resident};
use slipcase_open::{recover, session};

/// Open the payload of a Slipcase container in its own application, and write
/// edits back into the container.
///
/// Two lines rather than the table, and under `--help` rather than `-h`.
/// Concept 9 keeps the command line the floor beneath everything else, and the
/// thing read most often is the list of verbs: every line spent on paths here
/// is a line between somebody and the verb they came for. `policy` is where the
/// paths belong, because it prints the ones this machine resolves rather than
/// the ones this build documents.
///
/// Wrapped by hand. `clap` reflows help text only with its `wrap_help`
/// feature, which pulls in a terminal-size crate and is not enabled here, so an
/// unbroken line would run off the side of the screen rather than off the side
/// of nothing.
#[derive(Parser)]
#[command(
    version,
    about,
    long_about = None,
    // So that a lone container path is not read as a malformed subcommand.
    args_conflicts_with_subcommands = true,
    after_long_help = "Settings are read from /etc/slipcase/open.toml and from\n\
$XDG_CONFIG_HOME/slipcase-open/policy.toml, neither of which has to exist.\n\
Run `slipcase-open policy` for the paths on this machine, or see\n\
slipcase-open(1)."
)]
struct Cli {
    #[command(subcommand)]
    verb: Option<Verb>,
    /// A container to open, for an invocation that names no verb.
    ///
    /// **The association is why this exists.** Concept 4 wants a double-click,
    /// and on Windows a packaged handler is launched with the file path and
    /// nothing else — there is no place in a manifest to put a verb in front of
    /// it, the way `Exec=slipcase-open open %f` does in the desktop entry. So a
    /// lone path means `open`, which is also what every other document handler
    /// does and is no worse a command line for it.
    container: Option<PathBuf>,
}

#[derive(Subcommand)]
enum Verb {
    /// Open a container's payload and watch for edits.
    Open(Open),
    /// List the sessions on this machine and what became of each.
    Sessions,
    /// Close an open session, writing back what it has.
    Close(Close),
    /// Act on a session left behind by one that did not close.
    Recover(Recover),
    /// Where settings are read from, and what they add up to.
    Policy,
}

#[derive(Args)]
struct Open {
    /// The container to open.
    container: PathBuf,
}

#[derive(Args)]
struct Close {
    /// The session, as `sessions` lists it.
    id: String,
}

#[derive(Args)]
struct Recover {
    /// The session, as `sessions` lists it.
    id: String,
    /// Put the payload back into its container.
    #[arg(long, conflicts_with = "discard")]
    write_back: bool,
    /// Throw the payload away and remove the session.
    #[arg(long)]
    discard: bool,
}

type Fallible = Result<(), Box<dyn std::error::Error>>;

/// A failure whoever needed to see it has already seen.
///
/// Concept 9 puts the instance's narration on its channel, and on a machine
/// with no notification service that channel is this program's own error
/// stream. A refusal reported there and then returned to the top would appear
/// in it twice. This carries the exit code and no second sentence.
#[derive(Debug)]
struct AlreadySaid;

impl std::fmt::Display for AlreadySaid {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("reported through the channel")
    }
}

impl std::error::Error for AlreadySaid {}

fn main() -> ExitCode {
    #[cfg(windows)]
    attach_console();
    let cli = Cli::parse();
    // A verb, or a bare path meaning `open`, or neither — and neither is what
    // clap used to refuse for us, so it is refused here in the same shape.
    let verb = match (cli.verb, cli.container) {
        (Some(verb), _) => Some(verb),
        (None, Some(container)) => Some(Verb::Open(Open { container })),
        // Neither a verb nor a container. On Windows that is the Start tile
        // being clicked, which concept 12 answers with the standing list; a
        // packaged application whose tile does nothing is the first thing a
        // person sees of it. Everywhere else it is a command missing its verb.
        (None, None) => {
            if cfg!(windows) {
                None
            } else {
                let _ = Cli::command().print_help();
                return ExitCode::from(2);
            }
        }
    };
    let outcome = || -> Fallible {
        let root = session::default_root()?;
        let door = endpoint::path()?;
        match verb {
            Some(Verb::Open(a)) => open(&root, &door, &a),
            Some(Verb::Sessions) => sessions(&root, &door),
            Some(Verb::Close(a)) => close(&door, &a),
            Some(Verb::Recover(a)) => recover_one(&root, &door, &a),
            Some(Verb::Policy) => settings(&root, &door),
            None => stand_by(&root, &door),
        }
    }();
    match outcome {
        Ok(()) => ExitCode::SUCCESS,
        Err(e) => {
            if e.downcast_ref::<AlreadySaid>().is_none() {
                eprintln!("slipcase-open: {e}");
            }
            ExitCode::FAILURE
        }
    }
}

/// Hand a request to the instance, if there is one.
///
/// `Ok(None)` means nobody is listening, which is not a failure: it is the
/// common case for the first invocation.
fn hand_over(door: &Path, request: &Request) -> Result<Option<Response>, ipc::Error> {
    // Before the request goes anywhere. This process is the one the shell just
    // activated, so it is the one holding the right to put a window in front,
    // and the instance about to do the launching is not. `platform::shell` says
    // what that costs when it is left here.
    #[cfg(windows)]
    slipcase_open::platform::hand_the_foreground_on();
    match endpoint::connect(door) {
        Err(_) => Ok(None),
        Ok(mut stream) => ipc::ask(&mut stream, request).map(Some),
    }
}

fn say(response: &Response) -> Fallible {
    match response {
        Response::Ok(lines) => {
            for line in lines {
                println!("{line}");
            }
            Ok(())
        }
        Response::Err(why) => Err(why.clone().into()),
    }
}

fn open(root: &Path, door: &Path, a: &Open) -> Fallible {
    // Resolved here, so that what goes over the wire and what the instance
    // matches against its table are the same file rather than two spellings.
    let container = std::fs::canonicalize(&a.container)
        .map_err(|e| format!("{}: {e}", a.container.display()))?;
    // Concept 9. A double-click has no terminal, so the lines handed back here
    // go nowhere and the instance has to speak for this invocation. Asked of
    // the error stream because that is where this program's own messages go; a
    // run whose output is piped into something still has somewhere to show a
    // refusal.
    let voice = if std::io::stderr().is_terminal() {
        Voice::Client
    } else {
        Voice::Instance
    };
    let request = Request::Open { container, voice };

    if let Some(response) = hand_over(door, &request)? {
        return say(&response);
    }

    // Nobody is listening, so become the instance. Losing the race to bind
    // means somebody else became it between the connect and the bind, and the
    // answer is to hand over to them rather than to fail.
    let listener = match endpoint::bind(door) {
        Ok(listener) => listener,
        Err(why) => {
            // Where nobody answers either, the bind error is the only account
            // of what went wrong, and it is worth more than a sentence saying
            // that neither worked. A socket path over the platform's length
            // limit fails both halves and reads as a mystery without it.
            return match hand_over(door, &request)? {
                Some(response) => say(&response),
                None => Err(format!(
                    "could not reach the instance, and {} could not be bound: {why}",
                    door.display()
                )
                .into()),
            };
        }
    };

    let channel = channel();
    let source = policy::for_this_platform();
    // Resolved once and held for the life of the instance. Concept 10's warning
    // about caching is about what may be opened, where a value held across a
    // policy push is a bypass; `flow::open` still resolves the lists itself on
    // every launch. How loud to be gates no decision.
    let volume = policy::resolve(&source)
        .map(|e| e.notify)
        .unwrap_or_default();
    let outside = Outside::new(&source, &Host, channel.as_ref()).saying(volume);
    report_policy(&outside);

    // Before the first session, and with nothing live yet to protect. Concept
    // 6.3: an unchanged leftover means nothing was lost, so it goes quietly.
    let _ = resident::sweep(root, &[]);

    let mut instance = Resident::new(root);
    let response = instance.handle(request, &outside);

    // **Raised before the exit decision rather than after it**, because whether
    // there is anywhere for a trouble to live is part of that decision.
    //
    // Concept 5.1 says a refused container is told about in a way that cannot
    // be missed "and the standing list carries it afterwards in the one colour
    // reserved for it". A refusal holds no session, so concept 8's exit rule
    // was ending this process before the icon existed. Measured on 2026-09-06
    // against the installed package: on a cold double-click of a container that
    // must be refused, `standing` was never reached at all, while the same
    // double-click with an instance already running showed the icon — because
    // that icon belonged to the earlier invocation.
    //
    // An `open` that succeeded has a session and is not idle, so this raises
    // nothing that is then immediately put down.
    let standing = standing();
    if !has_a_reason_to_stay(
        instance.is_idle(),
        !instance.troubles().is_empty(),
        standing.holding(),
    ) {
        // Nothing was started and nothing is being held, so there is no reason
        // to keep the front door. Dropped before the wait below and not after:
        // a refusal that somebody leaves on screen would otherwise hold a bound
        // door that nothing is accepting on, and the next double-click would
        // fail to reach an instance that is not there.
        drop(listener);
        // **But a refusal this invocation raised may still be on the screen.**
        // Concept 12's box belongs to this process, and returning here is what
        // took it down before it had drawn — the whole of the defect measured
        // on 2026-09-06, where the first container refused after a restart said
        // nothing at all and the second, with an instance by then running, said
        // it every time.
        see_out(&outside);
        return match &response {
            Response::Ok(_) => say(&response),
            Response::Err(_) if voice == Voice::Client => say(&response),
            Response::Err(_) => Err(AlreadySaid.into()),
        };
    }

    // Something is being held, and a refusal can be what is holding it.
    // Concept 8 has an open on a container with a session left behind refuse
    // *and* raise the recovery question, so returning on the refusal would end
    // the process that the question's buttons have to reach — and end it
    // without withdrawing them, which is the one thing `stand_down` exists to
    // prevent. The refusal is reported and the loop runs anyway.
    match &response {
        Response::Ok(_) => say(&response)?,
        // Said once, on the same rule the instance narrates by: where the voice
        // is the instance's, it has already gone through the channel — which on
        // a machine with no notification service is this same error stream.
        Response::Err(why) if voice == Voice::Client => eprintln!("slipcase-open: {why}"),
        Response::Err(_) => {}
    }

    if std::io::stderr().is_terminal() {
        // Only where somebody is looking at it. As a notification this would be
        // the tool announcing that it had started, which is what the document
        // opening already said.
        eprintln!("Watching. Interrupt to leave the sessions recoverable, or:");
        eprintln!("  slipcase-open close <session>");
    }

    resident::run(listener, &mut instance, &outside, standing.as_ref())?;
    instance.stand_down(&outside);
    // The loop can have insisted on its last turn, and the same rule applies on
    // the way out of it as on the way out above.
    see_out(&outside);
    Ok(())
}

/// Do not end while a refusal this process raised is still on the screen.
///
/// **Only where the dialog is the whole of what was said.** At a command line
/// the refusal is already in the text and in the exit code, so a modal that
/// outlived the command would be the worse failure of the two: measured on
/// 2026-09-06, a packaged `slipcase-open open` with its output redirected sat
/// waiting on a box nobody was there to close, which is a script stopped dead.
/// A double-click has no text and no exit code anybody reads, and there the box
/// is the entire message — see [`present::Channel::stay_until_seen`].
///
/// The same question as [`standing`] asks, and deliberately the same answer:
/// who started this decides both whether an icon is raised and whether a dialog
/// is worth waiting for.
fn see_out(outside: &Outside<'_>) {
    if !from_a_command_line() {
        outside.channel.stay_until_seen();
    }
}

/// Concept 8's exit rule, as concept 5.1 and [`present::Standing::holding`]
/// amend it.
///
/// Three facts and no state, so that the rule can be read and tested rather
/// than traced through `open`. *Idle* is concept 8's own: no open session, none
/// lingering, no question outstanding. *Carrying* is something the icon has
/// gone a colour for. *Showing* is whether this invocation has a surface that
/// colour can appear on — which at a prompt it has not, and there the command
/// line is the standing list and `open` must still return with the refusal's
/// own exit code.
const fn has_a_reason_to_stay(idle: bool, carrying: bool, showing: bool) -> bool {
    !idle || (carrying && showing)
}

/// Concept 9's channel, or the floor beneath it.
///
/// The fallback is silent on purpose. A machine with no session bus is one
/// where the command line is the interface, and announcing that the
/// notifications are unavailable would be the first thing this tool said on
/// every run in an SSH session.
fn channel() -> Box<dyn Channel> {
    #[cfg(target_os = "linux")]
    if let Ok(desktop) = present::freedesktop::Desktop::connect() {
        return Box::new(desktop);
    }
    #[cfg(windows)]
    if let Ok(toast) = present::toast::Toast::connect() {
        return Box::new(toast);
    }
    Box::new(present::terminal::Terminal)
}

fn sessions(root: &Path, door: &Path) -> Fallible {
    // The instance knows which are open; the state directory knows what was
    // left. Ask whoever can answer.
    if let Some(response) = hand_over(door, &Request::List)? {
        return say(&response);
    }
    let found = session::scan(root)?;
    if found.is_empty() {
        println!("No sessions.");
        return Ok(());
    }
    for s in &found {
        let state = recover::state(s);
        println!(
            "{}  {}  {}",
            id_of(s),
            slpc::display_name(&s.record().payload),
            state
        );
        println!("    from {}", slpc::display_path(&s.record().container));
        // Anything that is not quiet, which is a wider set than the ones
        // needing a decision. Concept 6.3 as amended writes an edit back when
        // its container is next opened, and this is concept 9's floor: what it
        // owes somebody reading it is the verb that does the same thing now,
        // whether or not the tool would eventually get there by itself.
        match state.course() {
            recover::Course::Sweep => {}
            recover::Course::WriteBack => println!(
                "    goes back when its container is next opened, or: \
                 slipcase-open recover {} --write-back",
                id_of(s)
            ),
            recover::Course::Ask => println!(
                "    slipcase-open recover {} --write-back|--discard",
                id_of(s)
            ),
        }
    }
    Ok(())
}

fn close(door: &Path, a: &Close) -> Fallible {
    match hand_over(door, &Request::Close(a.id.clone()))? {
        Some(response) => say(&response),
        None => Err("no instance is running, so nothing is open to close".into()),
    }
}

fn recover_one(root: &Path, door: &Path, a: &Recover) -> Fallible {
    // A session the instance is holding is not recovery's to touch: its watcher
    // is live and its payload may be mid-save. `close` is the verb for that,
    // and it writes back on the way out.
    if let Some(Response::Ok(lines)) = hand_over(door, &Request::List)? {
        if lines
            .iter()
            .any(|l| l.starts_with(&a.id) && l.contains("open,"))
        {
            return Err(format!(
                "{} is open, not left behind. Use `slipcase-open close {}`.",
                a.id, a.id
            )
            .into());
        }
    }

    let mut s = session::find(root, &a.id)?;
    if a.discard {
        s.remove()?;
        println!("Discarded.");
        return Ok(());
    }
    if !a.write_back {
        // Concept 6.3: recovery reports and never acts. Naming a session
        // without saying what to do with it is a question, so this answers it
        // rather than choosing.
        println!("{}: {}", a.id, recover::state(&s));
        println!("Pass --write-back or --discard to act on it.");
        return Ok(());
    }
    slipcase_open::writeback::write_back(&mut s)?;
    println!(
        "Written back to {}.",
        slpc::display_path(&s.record().container)
    );
    s.remove()?;
    Ok(())
}

/// Concept 10 asks the interface to say when settings are administered, both to
/// set expectations and to keep somebody from reporting that the application
/// randomly refuses to open files.
///
/// Nothing is said where policy cannot be read: the open below resolves it
/// again — it has to, because §10 puts the decision in the launch path and not
/// in whatever ran before it — and refuses with that reason. Two copies of one
/// message is noise.
fn report_policy(outside: &Outside<'_>) {
    let Ok(effective) = policy::resolve(outside.policy) else {
        return;
    };
    if effective.managed {
        let mut report = Report::ordinary("Settings on this machine are administered.");
        if effective.configuration_suppressed {
            report = report.and("Your own configuration is not being consulted.");
        }
        outside.report(&report);
    }
    for entry in &effective.uncomparable_entries {
        outside.report(&Report::ordinary(format!(
            "Ignored: `{entry}` in a policy list cannot match any payload."
        )));
    }
}

/// Concept 10's layers, named rather than described.
///
/// **The paths are resolved and not documented.** Every one of them comes out
/// of the environment — `XDG_CONFIG_HOME` and `XDG_STATE_HOME` here,
/// `%LOCALAPPDATA%` and a registry subtree elsewhere — so the file somebody
/// should edit and the file this build's documentation names are two
/// questions, and only the running program can answer the first. `git config
/// --show-origin` and `npm config ls -l` exist for the same reason.
///
/// **Read here rather than asked of the instance.** There is nothing for the
/// front door to add: concept 10 forbids holding what may be opened, so a
/// running instance re-reads these files on every launch as well. The one
/// value it does keep is how loud to be, and that is what it read when it
/// started rather than what is in the file now — which is a reason to print
/// the file's answer here rather than the instance's.
///
/// **The locations are printed before anything is resolved.** A file with a
/// typo in it still has to be *named* by the verb somebody ran to find out
/// where their settings are, and a resolution that refuses to guess past a
/// broken layer would otherwise print nothing at all. The refusal follows the
/// listing and still fails the run.
fn settings(root: &Path, door: &Path) -> Fallible {
    let source = policy::for_this_platform();
    let resolved = policy::resolve(&source);

    let layers = source.locations();
    if layers.is_empty() {
        // macOS, until PLAN.md Phase 5. Said out loud, because a blank heading
        // reads as a program that could not find its own configuration.
        println!("No settings on this platform yet. The built-in set is what decides.");
    } else {
        println!("Where settings are read, in order of authority:");
        println!();
        for (origin, where_it_is) in layers {
            println!(
                "  {:<14}  {} ({})",
                origin,
                where_it_is,
                layer_state(&source, origin, resolved.as_ref().ok())
            );
        }
        println!();
    }

    // Named by the error itself, which carries the path. Returned rather than
    // reported, because a policy that cannot be established is not a policy
    // that permits and the exit code has to say so.
    let effective = resolved?;

    println!("What they add up to:");
    println!();
    let allowed: Vec<&str> = effective.allowed().collect();
    let denied: Vec<&str> = effective.denied().collect();
    labelled("allowed", &allowed);
    labelled("denied", &denied);
    println!(
        "  {:<14}  {}",
        "notify",
        // The spelling the file itself uses, so that what this prints can be
        // typed back into it.
        match effective.notify {
            policy::Notify::Everything => "everything",
            policy::Notify::Important => "important",
        }
    );
    println!(
        "  {:<14}  {}",
        "write-back",
        if effective.confirm_each_write_back {
            "confirmed each time"
        } else {
            "as the payload is saved"
        }
    );
    println!();

    // The same sentences `report_policy` says on the way into an open, because
    // somebody who saw one there and came here to find out more should meet
    // the claim they arrived with rather than a paraphrase of it.
    if effective.managed {
        println!("Settings on this machine are administered.");
        if effective.configuration_suppressed {
            println!("Your own configuration is not being consulted.");
        }
        println!();
    }
    for entry in &effective.uncomparable_entries {
        println!("Ignored: `{entry}` in a policy list cannot match any payload.");
    }
    if !effective.uncomparable_entries.is_empty() {
        println!();
    }

    // Not policy, and here anyway. Somebody looking for where this tool keeps
    // its files has one question and not two, and the answer to the half that
    // is not configuration is a directory nothing else prints.
    println!("Where the tool keeps its own state:");
    println!();
    println!("  {:<14}  {}", "sessions", slpc::display_path(root));
    println!("  {:<14}  {}", "front door", slpc::display_path(door));
    let (speaks, refused) = how_it_speaks();
    println!("  {:<14}  {speaks}", "notifications");
    if let Some(why) = refused {
        // Named here and nowhere else. Concept 9's fallback is silent on
        // purpose — announcing it on every run would be the first thing this
        // tool said in an SSH session — but *where settings are read from* is
        // exactly the verb somebody reaches for when they are asking why
        // nothing appeared, and a silence with no account of itself is the
        // thing that cannot be debugged.
        println!("  {:<14}  {why}", "");
    }
    Ok(())
}

/// Concept 12's standing session list, where the platform has somewhere to put
/// one.
///
/// The same shape as [`channel`]: try the platform's, and fall back to the one
/// that does nothing. A session with no shell has no tray, and that is ordinary.
/// **Where a terminal started this there is no icon, and that is concept 9's
/// own rule rather than a special case.** The command line is the floor beneath
/// the tray: a terminal *is* a standing list, `sessions` answers the same
/// question, and the person is looking at the output already. It is also what
/// keeps concept 8's exit rule intact for that invocation — an `open` typed at
/// a prompt that never returned would be a worse command than the one it
/// replaced.
///
/// Everything else — a double-click, the Start tile, the context menu — has no
/// terminal and gets an icon that stays until it is asked to leave.
fn standing() -> Box<dyn present::Standing> {
    if from_a_command_line() {
        return Box::new(present::Nowhere);
    }
    #[cfg(windows)]
    if let Ok(tray) = present::tray::Tray::show_up() {
        return Box::new(tray);
    }
    Box::new(present::Nowhere)
}

/// Whether a command line started this, which is the rule above written as a
/// question this process can actually answer.
///
/// **Not `is_terminal`, and that is a correction.** `is_terminal` asks whether
/// the error stream is a screen, and a shell that redirects one —
/// `slipcase-open open x.slpc 2> log`, or this crate's own process tests, which
/// spawn with pipes — is a command line that answers no. Measured on
/// 2026-09-06: with the standing list raised before the exit decision, a piped
/// refusal was given a tray and stayed, and two of `tests/the_process.rs` hung
/// on it. While the icon was only raised for an invocation that was staying
/// anyway, the wrong answer here cost nothing and so went unnoticed.
///
/// Explorer gives a windows-subsystem process no standard handles at all, and
/// [`attach_console`] has already joined the parent's console where there was
/// one — so *were we given an error stream* separates a shell from a
/// double-click where *is it a screen* does not.
///
/// [`Voice`] keeps `is_terminal`, and that is not an inconsistency: it asks
/// whether somebody is reading the lines handed back, which a redirect really
/// does change.
#[cfg(windows)]
#[allow(unsafe_code)]
fn from_a_command_line() -> bool {
    use windows::Win32::System::Console::{GetStdHandle, STD_ERROR_HANDLE};
    // SAFETY: a plain query against this process's own handle table.
    unsafe { GetStdHandle(STD_ERROR_HANDLE).is_ok_and(|h| !h.is_invalid()) }
}

/// Nowhere else has a tray for this to gate, so the narrower question is the
/// right one: concept 12 makes the command line the standing list on Linux
/// whatever this answers.
#[cfg(not(windows))]
fn from_a_command_line() -> bool {
    std::io::stderr().is_terminal()
}

/// Stand by with the standing list and nothing open.
///
/// **The Start tile**, which is what a packaged product does when somebody
/// clicks the thing they installed and has no container in mind. It puts up the
/// icon and waits; every other invocation reaches the same place by opening
/// something first.
///
/// The earlier version of this comment claimed this was the *only* invocation
/// that outlived its work, on the reasoning that otherwise every container
/// opened in a morning would leave an icon behind. That reasoning was wrong,
/// and the front door is why: the second double-click hands over to the
/// instance the first one started, so a morning's containers are one icon and
/// not ten. There was never a pile to prevent.
///
/// Where an instance is already running there is already an icon, so this hands
/// over and says nothing rather than raising a second one.
///
/// # Errors
///
/// Where the front door cannot be bound or served.
fn stand_by(root: &Path, door: &Path) -> Fallible {
    #[cfg(windows)]
    {
        if hand_over(door, &Request::Ping)?.is_some() {
            return Ok(());
        }
        let Ok(listener) = endpoint::bind(door) else {
            // Somebody bound it between the two calls, which means there is an
            // instance and therefore a tray. Nothing to say.
            return Ok(());
        };
        let channel = channel();
        let source = policy::for_this_platform();
        let volume = policy::resolve(&source)
            .map(|e| e.notify)
            .unwrap_or_default();
        let outside = Outside::new(&source, &Host, channel.as_ref()).saying(volume);
        let _ = resident::sweep(root, &[]);
        let mut instance = Resident::new(root);
        let standing = standing();
        resident::run(listener, &mut instance, &outside, standing.as_ref())?;
        instance.stand_down(&outside);
        see_out(&outside);
        Ok(())
    }
    #[cfg(not(windows))]
    {
        let _ = (root, door);
        Err("no standing list on this platform".into())
    }
}

/// Join the console of whoever started this, where there is one.
///
/// The windows subsystem means no console is made for this process, which is
/// what a double-click should get. An invocation from a terminal wants the
/// opposite, and `ATTACH_PARENT_PROCESS` is the difference between the two
/// without either having to be declared: a shell has a console to join and
/// Explorer does not.
///
/// Handles this process was given are left alone. A redirect — `slipcase-open
/// sessions > list.txt` — arrives as inherited handles, and joining a console
/// over the top of them would send the output somewhere the caller did not ask
/// for.
#[cfg(windows)]
#[allow(unsafe_code)]
fn attach_console() {
    use windows::Win32::System::Console::{
        AttachConsole, GetStdHandle, ATTACH_PARENT_PROCESS, STD_OUTPUT_HANDLE,
    };

    // SAFETY: both are plain queries against this process's own handle table.
    unsafe {
        if GetStdHandle(STD_OUTPUT_HANDLE).is_ok_and(|h| !h.is_invalid()) {
            return;
        }
        let _ = AttachConsole(ATTACH_PARENT_PROCESS);
    }
}

/// How this machine will be spoken to, and what refused a richer channel.
///
/// Asked by opening the channel and closing it again, which is the only honest
/// way to answer: the platform's own refusal is the answer, and anything else
/// would be this code guessing at what it would find.
fn how_it_speaks() -> (String, Option<String>) {
    #[cfg(target_os = "linux")]
    {
        match present::freedesktop::Desktop::connect() {
            Ok(_) => ("desktop notifications".to_owned(), None),
            Err(why) => ("the terminal".to_owned(), Some(why.to_string())),
        }
    }
    #[cfg(windows)]
    {
        match present::toast::Toast::connect() {
            Ok(_) => ("toast notifications".to_owned(), None),
            Err(why) => ("the terminal".to_owned(), Some(why.to_string())),
        }
    }
    #[cfg(not(any(target_os = "linux", windows)))]
    {
        ("the terminal".to_owned(), None)
    }
}

/// What reading this layer right now turns out to say.
///
/// A read rather than a look at the filesystem: a file that is there and holds
/// no key is a layer that exists and has no opinion, and telling somebody it is
/// *present* without telling them it sets nothing is how the shipped
/// `/etc/slipcase/open.toml` gets mistaken for a policy nobody wrote.
fn layer_state(
    source: &policy::Settings,
    origin: policy::Origin,
    effective: Option<&policy::Effective>,
) -> String {
    // Asked before the file is read, because a suppressed layer is not
    // consulted at all and saying what is in it would describe a file that
    // played no part in anything.
    if origin == policy::Origin::Configuration
        && effective.is_some_and(|e| e.configuration_suppressed)
    {
        return "not consulted; policy has suppressed it".to_string();
    }
    match policy::Source::layer(source, origin) {
        Ok(None) => "not there".to_string(),
        Ok(Some(layer)) if layer.says_nothing() => "there, and sets nothing".to_string(),
        Ok(Some(_)) => "in force".to_string(),
        // No detail, because the resolution below refuses with it and names the
        // same file. Two accounts of one typo is noise.
        Err(_) => "cannot be read".to_string(),
    }
}

/// A label and a list, wrapped under itself rather than off the side.
///
/// The permitted set is thirty entries before anybody configures anything,
/// which is one line nobody reads and three that anybody can.
fn labelled(label: &str, items: &[&str]) {
    const INDENT: usize = 18;
    const WIDTH: usize = 78;

    if items.is_empty() {
        println!("  {label:<14}  nothing");
        return;
    }
    let mut line = String::new();
    let mut first = true;
    for item in items {
        let piece = if line.is_empty() {
            (*item).to_string()
        } else {
            format!(", {item}")
        };
        if !line.is_empty() && INDENT + line.len() + piece.len() > WIDTH {
            println!("{}{line},", head(label, first));
            first = false;
            line = (*item).to_string();
        } else {
            line.push_str(&piece);
        }
    }
    println!("{}{line}", head(label, first));
}

/// The label on the first line of a wrapped list, and the space it occupied on
/// every line after it.
fn head(label: &str, first: bool) -> String {
    if first {
        format!("  {label:<14}  ")
    } else {
        " ".repeat(18)
    }
}

/// The name `sessions` prints and the other verbs take back.
fn id_of(s: &session::Session) -> String {
    s.dir()
        .file_name()
        .map_or_else(|| "?".to_string(), |n| n.to_string_lossy().into_owned())
}

#[cfg(test)]
mod tests {
    use super::has_a_reason_to_stay;

    /// The case the icon was missing from, which is the whole reason this rule
    /// is a function.
    #[test]
    fn a_refusal_stays_where_there_is_an_icon_to_carry_it() {
        // Nothing open, a trouble to show, and a tray to show it on.
        assert!(has_a_reason_to_stay(true, true, true));
    }

    #[test]
    fn the_same_refusal_at_a_prompt_returns() {
        // The command line is already the standing list, so there is no surface
        // to stay for and `open` must still hand back its exit code.
        assert!(!has_a_reason_to_stay(true, true, false));
    }

    #[test]
    fn an_open_session_stays_whether_or_not_anything_is_wrong() {
        // Concept 8's rule, untouched: the watcher is the reason, not the icon.
        for carrying in [false, true] {
            for showing in [false, true] {
                assert!(
                    has_a_reason_to_stay(false, carrying, showing),
                    "left with a session open ({carrying}, {showing})"
                );
            }
        }
    }

    #[test]
    fn nothing_held_and_nothing_wrong_returns_even_with_an_icon() {
        // A tray is not itself a reason for *this* decision. `resident::run`
        // keeps the instance alive once it is running, which is a different
        // question from whether it should start running at all.
        assert!(!has_a_reason_to_stay(true, false, true));
        assert!(!has_a_reason_to_stay(true, false, false));
    }
}