Skip to content

Commit d8a7230

Browse files
committed
Reworked app install setup + new custom env var support
1 parent a937ce4 commit d8a7230

30 files changed

Lines changed: 2659 additions & 154 deletions

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ The manifest shape (generation 1, `manifestVersion: 1`) is a locked public contr
318318
- A package using a newly added field must raise its `minimumMosVersion` to the release that introduced the field.
319319
- New template namespaces (for example a future `${smtp.*}`) follow the same rule: added to the validator and schema together, gated by `minimumMosVersion`.
320320
- Fields documented as provisional (`role`, `exports`, `integrations`, `configTargets`, `usefulness`, `homepage.widget`, `routes[].internalIcalBridge`) are outside the lock and may still change; do not present them to external authors as stable.
321+
- `${ownerEnv.*}` is a projection-only namespace that MOS generates into runtime projections for owner-set environment variables. It is not part of the manifest contract: package authors may never reference it, it stays out of `KNOWN_NAMESPACES` and the schema, and an authored manifest using it must keep failing validation.
321322
- Schema and validator move together: any change to `apps/manifest.schema.json` requires updating the manifest reference page and running `npm run apps:manifest:check` plus the backend unit tests.
322323

323324
### Catalog Privacy-Review Requirement

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@ Updater-facing software changes only — documentation, site, repository, and co
77
### Added
88

99
- Suite Manager keeps technical detail out of the way until you ask for it. Package ids, digests, ports, volume names, generated configuration and raw logs are no longer shown on healthy screens; **Settings → Technical controls** brings them all back, per owner and off by default. Diagnostics on a screen that is reporting a failure stay visible either way, so a bug report can still quote them.
10+
- Installed apps can now take environment variables you set yourself, under **Advanced details** in an app's **Settings** dialog. It is for values an app's own documentation asks for that MOS never knew to ask about — an upstream API credential, a feature switch — and it needs **Settings → Technical controls** switched on. Names MOS already manages are refused by name rather than silently ignored, hidden values are stored like any other app secret and never shown again, and MOS restarts the app and waits for it to answer: a value that stops it starting is rolled back to the previous environment on its own. Your variables survive app updates. **Compatibility:** a published privacy assessment describes an app as MOS ships it, so an app carrying your own variables says so on its posture panel.
1011
- Own-hardware installs now work on machines with more than one internal disk, which the installer previously refused outright. It lists every disk large enough in a stable order and says what each already holds — `empty - no partitions`, or `NOT EMPTY` with the filesystems and labels it found — so the spare drive is distinguishable from the one with your photos on it. Only the disk you pick is touched; the confirmation names it and its contents again before anything is erased.
1112

13+
### Changed
14+
15+
- Installing an app now opens the same review dialog for every app in the catalog, replacing the **Prepare** panel some apps showed and the bare **Install** button the rest had. It lists whatever the app needs from you, the web address it will get, and whether to put a shortcut on Homepage. Apps that need nothing say so rather than installing on the first click.
16+
- That dialog reopens as **Settings** once an app is running, and is no longer hidden behind technical controls. The values you gave at install are shown there as facts rather than editable fields: most are read only once, when the app first starts, so changing them afterwards would either do nothing or stop the app reaching its own data. Change them from inside the app itself.
17+
1218
### Fixed
1319

1420
- The own-hardware installer no longer turns a mistyped answer into a silent non-install. It numbers the choices, takes the erase confirmation as a separate step, and asks again on anything it does not recognise. Previously any answer other than exactly `YES` — a lowercase `yes` included — continued booting from the USB stick and brought the suite up there, which looked like a finished install on a machine that stopped booting the moment the stick came out. A machine running from the stick now says so on its console instead of reporting itself installed, and the stick is no longer expanded to fill itself, so it stays usable as an installer.

branding/styles/mos.css

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,252 @@ label:hover > .mos-switch-input:enabled + .mos-switch-track {
489489
}
490490
}
491491

492+
/* Settings row: the single element app configuration is built from. Label on
493+
the left, value on the right, a hairline between neighbours.
494+
495+
The whole vocabulary is one distinction: a value in text-strong can be typed
496+
into, a value in text-muted is a fact the owner cannot change. That is why
497+
there is no separate read-only control here — a disabled field says "not
498+
yet", and a fact has to say "this is how it is". Nothing is boxed, nothing is
499+
uppercase, and everything that is not the dialog title is one size. */
500+
.mos-rows {
501+
display: grid;
502+
}
503+
504+
.mos-row {
505+
display: grid;
506+
gap: var(--mos-space-1);
507+
padding: var(--mos-space-3) 0;
508+
/* Containing block for the visually hidden switch input, so focusing one
509+
inside a scrollable dialog cannot scroll the dialog somewhere else. */
510+
position: relative;
511+
border-top: var(--mos-border-hairline);
512+
}
513+
514+
/* Only the first row of the first group opens without a rule above it; a later
515+
group still starts with one, so the hairlines read as one continuous list
516+
however the groups are divided. */
517+
.mos-rows-lead > .mos-row:first-child {
518+
border-top: 0;
519+
}
520+
521+
label.mos-row {
522+
cursor: pointer;
523+
}
524+
525+
.mos-row-main {
526+
display: flex;
527+
align-items: center;
528+
justify-content: space-between;
529+
gap: var(--mos-space-4);
530+
min-height: 3rem;
531+
}
532+
533+
/* A row carrying a reason line gives the height to the line instead. */
534+
.mos-row-main:not(:only-child) {
535+
min-height: 2.4rem;
536+
}
537+
538+
.mos-row-label {
539+
flex: 0 0 auto;
540+
color: var(--mos-color-text-strong);
541+
}
542+
543+
.mos-row-trailing {
544+
display: flex;
545+
align-items: center;
546+
gap: var(--mos-space-2);
547+
min-width: 0;
548+
}
549+
550+
.mos-row-value {
551+
min-width: 0;
552+
color: var(--mos-color-text-muted);
553+
overflow: hidden;
554+
text-overflow: ellipsis;
555+
white-space: nowrap;
556+
}
557+
558+
.mos-row-code {
559+
font-family: var(--mos-font-mono);
560+
font-size: var(--mos-text-small);
561+
}
562+
563+
.mos-row-mask {
564+
letter-spacing: 0.14em;
565+
}
566+
567+
/* Editable: a real field, boxed like every other input in MOS. A row that is
568+
waiting for something the owner has not typed yet has to look unfinished,
569+
and a borderless value cannot — you cannot see the absence of a box that was
570+
never there. Facts stay plain text, so the box itself now carries the
571+
editable/fact distinction that colour alone used to. */
572+
.mos-row-input {
573+
flex: 1 1 14rem;
574+
min-width: 0;
575+
min-height: 2.9rem;
576+
padding: 0.72rem 0.8rem;
577+
border: 1px solid var(--mos-color-surface-border);
578+
border-radius: var(--mos-radius-sm);
579+
background: var(--mos-color-surface-strong);
580+
color: var(--mos-color-text-strong);
581+
font: inherit;
582+
transition:
583+
border-color var(--mos-transition),
584+
background var(--mos-transition);
585+
}
586+
587+
.mos-row-input::placeholder {
588+
color: var(--mos-color-text-muted);
589+
opacity: 1;
590+
}
591+
592+
.mos-row-input:focus-visible {
593+
outline: 3px solid var(--mos-color-focus-visible);
594+
outline-offset: 2px;
595+
}
596+
597+
/* Required and still empty: the reason the primary button is refusing. Warning
598+
rather than danger — nothing is wrong yet, there is just something left. */
599+
.mos-row-input-missing {
600+
border-color: var(--mos-color-warning-border);
601+
background: var(--mos-color-warning-soft);
602+
}
603+
604+
/* Required and given. The same accent the primary button uses, so a form that
605+
is ready to submit and the button that submits it agree. */
606+
.mos-row-input-filled {
607+
border-color: var(--mos-color-accent-border);
608+
background: var(--mos-color-accent-soft);
609+
}
610+
611+
/* A row the owner types into puts its label on its own line and gives the whole
612+
width to the box. Beside the label instead, the field is sized by whatever
613+
the label happens to be — a long one leaves a stub to type in, a short one a
614+
field wider than anything that goes in it — and the boxes stop lining up with
615+
each other, which is what made a list of them hard to read. */
616+
.mos-row-stacked > .mos-row-main {
617+
display: grid;
618+
/* Explicit, because the inline layout's justify-content: space-between would
619+
otherwise size this single column to its contents and leave the field as
620+
wide as whatever happens to be typed in it. minmax(0, 1fr) fills the row
621+
and still lets a long value scroll inside rather than push the row wider. */
622+
grid-template-columns: minmax(0, 1fr);
623+
gap: var(--mos-space-2);
624+
min-height: 0;
625+
}
626+
627+
/* Rows that stay side by side still let a boxed field drop to its own line
628+
rather than be squeezed to nothing. */
629+
.mos-row-main:has(.mos-row-input) {
630+
flex-wrap: wrap;
631+
}
632+
633+
@media (prefers-reduced-motion: reduce) {
634+
.mos-row-input {
635+
transition: none;
636+
}
637+
}
638+
639+
.mos-row-help {
640+
display: block;
641+
margin: 0;
642+
color: var(--mos-color-text-muted);
643+
font-size: var(--mos-text-small);
644+
line-height: 1.4;
645+
}
646+
647+
/* The line that stops an owner typing a password they can never change. */
648+
.mos-row-help-permanent {
649+
color: var(--mos-color-warning);
650+
}
651+
652+
.mos-row-invalid {
653+
color: var(--mos-color-danger);
654+
}
655+
656+
/* Inline action inside a row — copy, reveal, remove. Quiet enough that a row
657+
full of them still reads as a list of values rather than a toolbar. */
658+
.mos-row-action {
659+
display: inline-grid;
660+
place-items: center;
661+
flex: 0 0 auto;
662+
width: 2rem;
663+
height: 2rem;
664+
border: 0;
665+
border-radius: var(--mos-radius-sm);
666+
background: transparent;
667+
color: var(--mos-color-text-muted);
668+
cursor: pointer;
669+
transition:
670+
background var(--mos-transition),
671+
color var(--mos-transition);
672+
}
673+
674+
.mos-row-action:hover {
675+
background: var(--mos-color-surface-muted);
676+
color: var(--mos-color-accent);
677+
}
678+
679+
.mos-row-action-danger:hover {
680+
color: var(--mos-color-danger);
681+
}
682+
683+
.mos-row-action:disabled {
684+
cursor: not-allowed;
685+
opacity: 0.55;
686+
}
687+
688+
/* The whole row is the hit target for a switch, so the hover affordance comes
689+
from the row rather than from the track's own three rem. */
690+
label.mos-row:hover .mos-switch-input:enabled + .mos-switch-track {
691+
border-color: var(--mos-color-accent-border);
692+
}
693+
694+
/* A disclosure wearing the row's clothes: same height, same hairline, chevron
695+
where a value would be. Used so "Advanced details" is one more row in the
696+
list rather than a differently shaped thing bolted underneath it. */
697+
.mos-row-disclosure {
698+
border-top: var(--mos-border-hairline);
699+
padding-top: 0;
700+
}
701+
702+
.mos-row-disclosure > summary {
703+
display: flex;
704+
align-items: center;
705+
justify-content: space-between;
706+
gap: var(--mos-space-4);
707+
min-height: 3rem;
708+
padding: var(--mos-space-3) 0;
709+
color: var(--mos-color-text-strong);
710+
font-weight: var(--mos-weight-regular);
711+
list-style: none;
712+
cursor: pointer;
713+
}
714+
715+
.mos-row-disclosure > summary::-webkit-details-marker {
716+
display: none;
717+
}
718+
719+
.mos-row-chevron {
720+
display: inline-grid;
721+
place-items: center;
722+
flex: 0 0 auto;
723+
color: var(--mos-color-text-muted);
724+
transition: transform var(--mos-transition);
725+
}
726+
727+
.mos-row-disclosure[open] > summary .mos-row-chevron {
728+
transform: rotate(90deg);
729+
}
730+
731+
@media (prefers-reduced-motion: reduce) {
732+
.mos-row-action,
733+
.mos-row-chevron {
734+
transition: none;
735+
}
736+
}
737+
492738
.mos-panel {
493739
border: var(--mos-border-strong);
494740
border-radius: var(--mos-radius-md);

0 commit comments

Comments
 (0)