create-pmos 0.84.0 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/guide/command-palette.md +4 -2
- package/workspace/guide/my-work.md +7 -1
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/components.css +236 -12
- package/workspace/public/pm/dest-mywork.js +87 -29
- package/workspace/public/pm/palette-copy.js +4 -0
- package/workspace/public/pm/palette.js +39 -8
- package/workspace/public/pm/primitives.js +8 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.86.0",
|
|
4
4
|
"description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
package/payload/KIT-VERSION
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Eval calibration log"
|
|
4
4
|
description: "Append-only record of every PM override of an evaluator verdict."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-07T16:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-07T16:27:16Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Health budgets"
|
|
4
4
|
description: "Closed registry of health-budget anchors (D59), edited only by the PM."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-07T16:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-07T16:27:16Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Discoveries and friction log"
|
|
4
4
|
description: "Append-only record of what runs discovered and where the process rubbed."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-07T16:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-07T16:27:16Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Command palette"
|
|
4
4
|
description: "How to use the ⌘K palette: run a command, see its command-line form, keep a note, ask the bound session, search Knowledge, Memory and the Guide, and use the compose line on a phone."
|
|
5
|
-
timestamp: 2026-10-
|
|
5
|
+
timestamp: 2026-10-07
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Command palette
|
|
9
9
|
|
|
10
10
|
The command palette is one box for three jobs: open something, run a command, or keep a note. Press **⌘K** (Ctrl+K on Windows and Linux) to open it. Press **Esc** to close it.
|
|
11
11
|
|
|
12
|
+
On a desktop, the palette is a dialog in the middle of the screen. The search box is at the top with a search icon, the scope switch is under it, and a footer at the bottom shows a hint and the keys. On a phone it is a sheet (see "On a phone").
|
|
13
|
+
|
|
12
14
|
## Rows
|
|
13
15
|
|
|
14
|
-
Type in the box. The rows change as you type.
|
|
16
|
+
Type in the box. The rows change as you type. Rows of one kind sit under one small label, for example Commands or Initiatives, and each row has an icon for its kind. The selected row has a ring and an arrow. The rows come in this order:
|
|
15
17
|
|
|
16
18
|
1. **Matched rows.** A command, an initiative, a repository, a session, a tab or a view whose name matches what you typed. A command shows its command-line form next to its name, for example `pmos item`. You can run the same command from a terminal.
|
|
17
19
|
2. **Capture as note.** Keeps what you typed as a note.
|
|
@@ -10,6 +10,12 @@ Open My work from the top bar, from ⌘K (**My work**), or press `g w`.
|
|
|
10
10
|
|
|
11
11
|
My work lists every Jira ticket and every breakdown (WBS) item assigned to you, in all your registered repositories. It ranks them, shows what each one asks, and lets you give an agent an instruction for it. The agent's draft then waits for you in Agenda.
|
|
12
12
|
|
|
13
|
+
## The screen
|
|
14
|
+
|
|
15
|
+
The page has the same layout as Agenda. The title says how many items wait for a brief. At the right of the title, four buttons switch the list: **To brief**, **Agents**, **Ready** and **All**, each with its count. A strip below the title shows when Jira was last read and how many tickets and plan items the read holds. **Read again** is at the right of the strip.
|
|
16
|
+
|
|
17
|
+
Two cards fill the rest of the page. The left card lists the items. Each row has a dot for its tier (red for P1, amber for P2, grey for the rest). A badge shows when the item is due, and a **Decision** badge marks an item that looks like a decision only you can make. The right card, **Brief**, shows the open item. On a phone, the two cards stack.
|
|
18
|
+
|
|
13
19
|
## Where the items come from
|
|
14
20
|
|
|
15
21
|
- **Jira tickets.** The workspace does not read Jira. The **my-work-read** skill reads it in an agent session, with your own Atlassian connection. It reads the open issues assigned to you, with their comments and links, and writes a context card for each: who asked, what they ask, a short summary of the thread, the questions still open, and a suggested instruction for an agent. It does not copy the comments. Press **Read again** in My work to run it.
|
|
@@ -38,7 +44,7 @@ Being assigned a ticket does not mean an agent must resolve it. Every new item s
|
|
|
38
44
|
2. Change the instruction. It starts as the agent's suggestion.
|
|
39
45
|
3. Press **Run agent**. Or press **I'll handle it** to keep the item yourself.
|
|
40
46
|
|
|
41
|
-
Run agent adds one job to today's day run, with your instruction. The job runs like every Agenda job: at most 5 at once, one draft, and nothing is sent.
|
|
47
|
+
Run agent adds one job to today's day run, with your instruction. The job runs like every Agenda job: at most 5 at once, one draft, and nothing is sent. The **Decision** badge marks an item that looks like a decision only you can make. An agent can still prepare it.
|
|
42
48
|
|
|
43
49
|
**Back to brief** returns an item to To brief, unless an agent is working on it.
|
|
44
50
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.86.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pmos-workspace",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.86.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -696,6 +696,14 @@
|
|
|
696
696
|
height: auto;
|
|
697
697
|
max-height: calc(var(--pmc-vvh, 100dvh) - 24px);
|
|
698
698
|
}
|
|
699
|
+
.pmc-sheet--center {
|
|
700
|
+
inset: auto;
|
|
701
|
+
top: 84px;
|
|
702
|
+
left: 50%;
|
|
703
|
+
width: 640px;
|
|
704
|
+
max-height: 70vh;
|
|
705
|
+
transform: translateX(-50%);
|
|
706
|
+
}
|
|
699
707
|
.pmc-sheet__handle {
|
|
700
708
|
flex: none;
|
|
701
709
|
width: 36px;
|
|
@@ -723,11 +731,33 @@
|
|
|
723
731
|
letter-spacing: -0.01em;
|
|
724
732
|
overflow-wrap: anywhere;
|
|
725
733
|
}
|
|
734
|
+
.pmc-sheet__title--hidden {
|
|
735
|
+
position: absolute;
|
|
736
|
+
width: 1px;
|
|
737
|
+
height: 1px;
|
|
738
|
+
padding: 0;
|
|
739
|
+
margin: -1px;
|
|
740
|
+
overflow: hidden;
|
|
741
|
+
clip: rect(0, 0, 0, 0);
|
|
742
|
+
white-space: nowrap;
|
|
743
|
+
border-width: 0;
|
|
744
|
+
}
|
|
726
745
|
.pmc-sheet__sub {
|
|
727
746
|
font-size: 12px;
|
|
728
747
|
line-height: 16px;
|
|
729
748
|
color: var(--muted-foreground);
|
|
730
749
|
}
|
|
750
|
+
.pmc-sheet__sub--hidden {
|
|
751
|
+
position: absolute;
|
|
752
|
+
width: 1px;
|
|
753
|
+
height: 1px;
|
|
754
|
+
padding: 0;
|
|
755
|
+
margin: -1px;
|
|
756
|
+
overflow: hidden;
|
|
757
|
+
clip: rect(0, 0, 0, 0);
|
|
758
|
+
white-space: nowrap;
|
|
759
|
+
border-width: 0;
|
|
760
|
+
}
|
|
731
761
|
.pmc-sheet__head .pmc-btn--icon {
|
|
732
762
|
margin: -4px -8px 0 0;
|
|
733
763
|
}
|
|
@@ -741,6 +771,16 @@
|
|
|
741
771
|
overscroll-behavior: contain;
|
|
742
772
|
padding: 16px;
|
|
743
773
|
}
|
|
774
|
+
.pmc-sheet--center .pmc-sheet__body {
|
|
775
|
+
gap: 0;
|
|
776
|
+
padding: 0;
|
|
777
|
+
overflow: hidden;
|
|
778
|
+
min-height: 0;
|
|
779
|
+
}
|
|
780
|
+
.pmc-sheet--center {
|
|
781
|
+
display: flex;
|
|
782
|
+
flex-direction: column;
|
|
783
|
+
}
|
|
744
784
|
.pmc-sheet__body > * {
|
|
745
785
|
flex: none;
|
|
746
786
|
}
|
|
@@ -3672,13 +3712,49 @@
|
|
|
3672
3712
|
overscroll-behavior: contain;
|
|
3673
3713
|
}
|
|
3674
3714
|
.pl-item[aria-selected="true"] {
|
|
3675
|
-
background: var(--accent);
|
|
3715
|
+
background: var(--accent) !important;
|
|
3716
|
+
box-shadow: 0 0 0 1px var(--ring);
|
|
3717
|
+
}
|
|
3718
|
+
.pl-icon-tile {
|
|
3719
|
+
display: flex;
|
|
3720
|
+
align-items: center;
|
|
3721
|
+
justify-content: center;
|
|
3722
|
+
width: 28px;
|
|
3723
|
+
height: 28px;
|
|
3724
|
+
border-radius: 6px;
|
|
3725
|
+
background: var(--muted);
|
|
3726
|
+
color: var(--muted-foreground);
|
|
3727
|
+
flex: none;
|
|
3728
|
+
}
|
|
3729
|
+
.pl-item[aria-selected="true"] .pl-icon-tile {
|
|
3730
|
+
color: var(--foreground);
|
|
3731
|
+
}
|
|
3732
|
+
.pl-enter-icon {
|
|
3733
|
+
display: flex;
|
|
3734
|
+
align-items: center;
|
|
3735
|
+
justify-content: center;
|
|
3736
|
+
width: 16px;
|
|
3737
|
+
height: 16px;
|
|
3738
|
+
color: var(--foreground);
|
|
3739
|
+
}
|
|
3740
|
+
.pl-eyebrow {
|
|
3741
|
+
font-family: var(--mono);
|
|
3742
|
+
font-size: 11px;
|
|
3743
|
+
line-height: 16px;
|
|
3744
|
+
letter-spacing: 0.04em;
|
|
3745
|
+
text-transform: uppercase;
|
|
3746
|
+
color: var(--muted-foreground);
|
|
3747
|
+
padding: 10px 8px 4px;
|
|
3676
3748
|
}
|
|
3677
3749
|
.pl-cli {
|
|
3678
3750
|
font-family: var(--mono);
|
|
3679
3751
|
font-size: 12px;
|
|
3680
3752
|
line-height: 16px;
|
|
3681
3753
|
color: var(--muted-foreground);
|
|
3754
|
+
padding: 2px 6px;
|
|
3755
|
+
border-radius: 6px;
|
|
3756
|
+
background: var(--background);
|
|
3757
|
+
box-shadow: 0 0 0 1px var(--border);
|
|
3682
3758
|
}
|
|
3683
3759
|
.pl-note {
|
|
3684
3760
|
margin: 0;
|
|
@@ -3689,6 +3765,74 @@
|
|
|
3689
3765
|
.pl-note--error {
|
|
3690
3766
|
color: var(--destructive);
|
|
3691
3767
|
}
|
|
3768
|
+
.pl-search {
|
|
3769
|
+
display: flex;
|
|
3770
|
+
align-items: center;
|
|
3771
|
+
gap: 10px;
|
|
3772
|
+
padding: 0 12px 0 16px;
|
|
3773
|
+
border-bottom: 1px solid var(--border);
|
|
3774
|
+
}
|
|
3775
|
+
.pl-search__icon {
|
|
3776
|
+
display: flex;
|
|
3777
|
+
color: var(--muted-foreground);
|
|
3778
|
+
}
|
|
3779
|
+
.pl-search > div {
|
|
3780
|
+
flex: 1 1 auto;
|
|
3781
|
+
}
|
|
3782
|
+
.pl-input.pl-input,
|
|
3783
|
+
.pl-input.pl-input:focus-visible {
|
|
3784
|
+
height: 52px;
|
|
3785
|
+
font-size: 16px;
|
|
3786
|
+
border: 0;
|
|
3787
|
+
outline: none;
|
|
3788
|
+
}
|
|
3789
|
+
.pl-kbd {
|
|
3790
|
+
font: 11px/16px var(--mono);
|
|
3791
|
+
color: var(--muted-foreground);
|
|
3792
|
+
padding: 0 6px;
|
|
3793
|
+
border-radius: 6px;
|
|
3794
|
+
box-shadow: 0 0 0 1px var(--border);
|
|
3795
|
+
}
|
|
3796
|
+
.pl-center .pl-scopes {
|
|
3797
|
+
align-self: flex-start;
|
|
3798
|
+
margin: 10px 16px 4px;
|
|
3799
|
+
}
|
|
3800
|
+
.pl-center .pl-list {
|
|
3801
|
+
flex: 1 1 auto;
|
|
3802
|
+
min-height: 0;
|
|
3803
|
+
max-height: none;
|
|
3804
|
+
padding: 4px 8px 8px;
|
|
3805
|
+
}
|
|
3806
|
+
.pl-center .pl-list .pl-item {
|
|
3807
|
+
border-bottom: 0;
|
|
3808
|
+
background: transparent;
|
|
3809
|
+
border-radius: 6px;
|
|
3810
|
+
min-height: 44px;
|
|
3811
|
+
padding: 6px 8px;
|
|
3812
|
+
}
|
|
3813
|
+
.pl-center .pl-list .pl-item > span:first-child {
|
|
3814
|
+
align-self: center;
|
|
3815
|
+
}
|
|
3816
|
+
.pl-foot {
|
|
3817
|
+
display: flex;
|
|
3818
|
+
align-items: center;
|
|
3819
|
+
gap: 12px;
|
|
3820
|
+
padding: 8px 16px;
|
|
3821
|
+
border-top: 1px solid var(--border);
|
|
3822
|
+
background: var(--background);
|
|
3823
|
+
}
|
|
3824
|
+
.pl-foot .pl-note {
|
|
3825
|
+
flex: 1 1 auto;
|
|
3826
|
+
min-width: 0;
|
|
3827
|
+
font-size: 12px;
|
|
3828
|
+
line-height: 16px;
|
|
3829
|
+
}
|
|
3830
|
+
.pl-keys {
|
|
3831
|
+
flex: none;
|
|
3832
|
+
font-size: 12px;
|
|
3833
|
+
line-height: 16px;
|
|
3834
|
+
color: var(--muted-foreground);
|
|
3835
|
+
}
|
|
3692
3836
|
/* the terminal's compose line (phone): one message field and Send, in the bottom bar */
|
|
3693
3837
|
.pl-compose {
|
|
3694
3838
|
display: flex;
|
|
@@ -5784,12 +5928,51 @@
|
|
|
5784
5928
|
}
|
|
5785
5929
|
.rp-mywork-top {
|
|
5786
5930
|
display: flex;
|
|
5787
|
-
flex-
|
|
5931
|
+
flex-direction: column;
|
|
5932
|
+
gap: 12px;
|
|
5933
|
+
}
|
|
5934
|
+
.rp-mywork-header {
|
|
5935
|
+
display: flex;
|
|
5936
|
+
justify-content: space-between;
|
|
5788
5937
|
align-items: center;
|
|
5789
5938
|
gap: 12px;
|
|
5790
5939
|
}
|
|
5791
|
-
.rp-mywork-
|
|
5792
|
-
|
|
5940
|
+
.rp-mywork-header h2 {
|
|
5941
|
+
flex: 1;
|
|
5942
|
+
}
|
|
5943
|
+
.rp-mywork-strip {
|
|
5944
|
+
display: flex;
|
|
5945
|
+
align-items: center;
|
|
5946
|
+
gap: 10px;
|
|
5947
|
+
padding: 8px 12px;
|
|
5948
|
+
border: 1px solid var(--border);
|
|
5949
|
+
border-radius: 8px;
|
|
5950
|
+
background: var(--card);
|
|
5951
|
+
}
|
|
5952
|
+
.rp-mywork-title {
|
|
5953
|
+
flex: 1;
|
|
5954
|
+
margin: 0;
|
|
5955
|
+
font-size: 20px;
|
|
5956
|
+
font-weight: 600;
|
|
5957
|
+
}
|
|
5958
|
+
.rp-mywork-strip > .rp-mywork-hint:first-child {
|
|
5959
|
+
color: var(--foreground);
|
|
5960
|
+
}
|
|
5961
|
+
.rp-mywork-row {
|
|
5962
|
+
--background: transparent;
|
|
5963
|
+
}
|
|
5964
|
+
.rp-mywork-spacer {
|
|
5965
|
+
flex: 1;
|
|
5966
|
+
}
|
|
5967
|
+
.rp-mywork-dot--p1 {
|
|
5968
|
+
background: var(--destructive);
|
|
5969
|
+
}
|
|
5970
|
+
.rp-mywork-dot--p2 {
|
|
5971
|
+
background: var(--warning);
|
|
5972
|
+
}
|
|
5973
|
+
.rp-mywork-dot--p3,
|
|
5974
|
+
.rp-mywork-dot--p4 {
|
|
5975
|
+
background: var(--muted-foreground);
|
|
5793
5976
|
}
|
|
5794
5977
|
.rp-mywork-body {
|
|
5795
5978
|
display: flex;
|
|
@@ -5798,32 +5981,69 @@
|
|
|
5798
5981
|
}
|
|
5799
5982
|
.rp-mywork-grid {
|
|
5800
5983
|
display: grid;
|
|
5801
|
-
grid-template-columns: minmax(
|
|
5802
|
-
gap:
|
|
5803
|
-
align-items:
|
|
5984
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr);
|
|
5985
|
+
gap: 12px;
|
|
5986
|
+
align-items: stretch;
|
|
5804
5987
|
}
|
|
5805
5988
|
.rp-mywork-list {
|
|
5806
5989
|
display: flex;
|
|
5807
5990
|
flex-direction: column;
|
|
5808
|
-
gap:
|
|
5991
|
+
gap: 0;
|
|
5809
5992
|
min-width: 0;
|
|
5810
5993
|
}
|
|
5811
5994
|
.rp-mywork-views {
|
|
5812
5995
|
border-bottom: 1px solid var(--border);
|
|
5996
|
+
margin-bottom: 8px;
|
|
5813
5997
|
}
|
|
5814
5998
|
.rp-mywork-row {
|
|
5815
|
-
border
|
|
5999
|
+
border: 1px solid transparent;
|
|
6000
|
+
border-radius: 6px;
|
|
5816
6001
|
}
|
|
5817
|
-
.rp-mywork-
|
|
5818
|
-
|
|
6002
|
+
.rp-mywork-grid > .td-day__panel,
|
|
6003
|
+
.rp-mywork-grid > .td-day__detail {
|
|
6004
|
+
align-content: start;
|
|
6005
|
+
}
|
|
6006
|
+
.rp-mywork-badge {
|
|
6007
|
+
gap: 4px;
|
|
6008
|
+
}
|
|
6009
|
+
.rp-mywork-row__inner {
|
|
6010
|
+
display: flex;
|
|
6011
|
+
align-items: center;
|
|
6012
|
+
gap: 10px;
|
|
6013
|
+
flex: 1;
|
|
6014
|
+
min-width: 0;
|
|
6015
|
+
}
|
|
6016
|
+
.rp-mywork-row__content {
|
|
6017
|
+
flex: 1;
|
|
6018
|
+
min-width: 0;
|
|
6019
|
+
}
|
|
6020
|
+
.rp-mywork-row__title {
|
|
6021
|
+
font-weight: 500;
|
|
6022
|
+
font-size: 14px;
|
|
6023
|
+
}
|
|
6024
|
+
.rp-mywork-row__meta {
|
|
6025
|
+
font-size: 12px;
|
|
6026
|
+
color: var(--muted-foreground);
|
|
5819
6027
|
}
|
|
5820
6028
|
.rp-mywork-row--open {
|
|
5821
|
-
|
|
6029
|
+
background: var(--accent);
|
|
6030
|
+
border-color: var(--ring);
|
|
6031
|
+
}
|
|
6032
|
+
.rp-mywork-dot {
|
|
6033
|
+
width: 8px;
|
|
6034
|
+
height: 8px;
|
|
6035
|
+
border-radius: 50%;
|
|
6036
|
+
flex: none;
|
|
6037
|
+
display: inline-block;
|
|
6038
|
+
}
|
|
6039
|
+
.rp-mywork-badge {
|
|
6040
|
+
flex: none;
|
|
5822
6041
|
}
|
|
5823
6042
|
.rp-mywork-state {
|
|
5824
6043
|
display: inline-flex;
|
|
5825
6044
|
align-items: center;
|
|
5826
6045
|
color: var(--muted-foreground);
|
|
6046
|
+
flex: none;
|
|
5827
6047
|
}
|
|
5828
6048
|
.rp-mywork-page {
|
|
5829
6049
|
display: flex;
|
|
@@ -5891,6 +6111,10 @@
|
|
|
5891
6111
|
.rp-mywork-grid {
|
|
5892
6112
|
grid-template-columns: 1fr;
|
|
5893
6113
|
}
|
|
6114
|
+
.rp-mywork-header {
|
|
6115
|
+
flex-direction: column;
|
|
6116
|
+
align-items: flex-start;
|
|
6117
|
+
}
|
|
5894
6118
|
}
|
|
5895
6119
|
|
|
5896
6120
|
/* -- layouts · .nt- Notes: one meeting on one screen — notes and what was caught beside a quiet transcript (meeting-notes Runs 1 and 2) -- */
|
|
@@ -4,15 +4,13 @@
|
|
|
4
4
|
* A DESTINATION MODULE (registry.js): mountDestination(host, ctx) on every render with the same host, disposeDestination(host).
|
|
5
5
|
* It keeps ONE persistent root per host and rebuilds the body only when its own data or a choice changed.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Every press is a page press to POST /api/my-work/act or /api/my-work/refresh. Approve, revise and retry are Agenda's
|
|
15
|
-
* (R7): there is no approve button here. In fixture mode it reads nothing.
|
|
7
|
+
* Layout (matching Agenda's two-column design):
|
|
8
|
+
* - header: "N to brief in <repo>" with segmented control (To brief/Agents/Ready/All with counts);
|
|
9
|
+
* - strip: Jira read line and Read again button;
|
|
10
|
+
* - body: two-column grid (desktop) or single column (mobile)
|
|
11
|
+
* - left: .td-day__panel card with list items (status dot, title, meta, trailing badge);
|
|
12
|
+
* - right: .td-day__detail card with the chosen item's full view.
|
|
13
|
+
* Every press is a page press to POST /api/my-work/act or /api/my-work/refresh. In fixture mode it reads nothing.
|
|
16
14
|
*/
|
|
17
15
|
|
|
18
16
|
import { el } from './dom.js';
|
|
@@ -71,6 +69,27 @@ const stateIcon = (x) => {
|
|
|
71
69
|
return el('span', { class: 'rp-mywork-state', role: 'img', 'aria-label': stateWord(x), title: stateWord(x), 'data-my-work-state': x.state }, node);
|
|
72
70
|
};
|
|
73
71
|
|
|
72
|
+
// Helper: create status dot colored by tier
|
|
73
|
+
const statusDot = (tier) => {
|
|
74
|
+
return el('span', { class: 'rp-mywork-dot rp-mywork-dot--' + String(tier).toLowerCase(), 'data-my-work-tier': tier, title: COPY.score({ tier }) });
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Trailing badges: a decision marker and a due date when one is set (soon = warning tone).
|
|
78
|
+
const dueLabel = (due) => {
|
|
79
|
+
const days = Math.round((new Date(due + 'T00:00:00') - new Date(new Date().toDateString())) / 86400000);
|
|
80
|
+
if (Number.isNaN(days)) return { text: 'Due ' + due, soon: false };
|
|
81
|
+
if (days < 0) return { text: 'Overdue', soon: true };
|
|
82
|
+
if (days === 0) return { text: 'Due today', soon: true };
|
|
83
|
+
if (days === 1) return { text: 'Due tomorrow', soon: true };
|
|
84
|
+
return { text: 'Due ' + new Date(due + 'T00:00:00').toLocaleDateString('en-US', { month: 'short', day: 'numeric' }), soon: false };
|
|
85
|
+
};
|
|
86
|
+
const trailingBadges = (x) => {
|
|
87
|
+
const out = [];
|
|
88
|
+
if (x.kind === 'decide' && x.state === 'brief') out.push(badge({ text: 'Decision', tone: 'outline', attrs: { title: COPY.decide, class: 'rp-mywork-badge' } }));
|
|
89
|
+
if (x.due) { const d = dueLabel(x.due); out.push(badge({ text: d.text, tone: d.soon ? 'warning' : 'outline', attrs: { class: 'rp-mywork-badge' } })); }
|
|
90
|
+
return out;
|
|
91
|
+
};
|
|
92
|
+
|
|
74
93
|
/* ── mount ───────────────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
75
94
|
|
|
76
95
|
const insts = new WeakMap();
|
|
@@ -128,12 +147,12 @@ function itemRow(inst, x) {
|
|
|
128
147
|
const meta = [x.source === 'jira' ? 'Jira ' + x.key : 'WBS ' + (x.repo ? x.repo + ' · ' : '') + x.key];
|
|
129
148
|
if (x.from) meta.push(COPY.from(x.from));
|
|
130
149
|
if (x.due) meta.push(COPY.due(x.due));
|
|
131
|
-
return el('div', { class: 'rp-mywork-row' + (open ? ' rp-mywork-row--open' : ''), role: 'listitem', 'data-my-work-item': x.id },
|
|
150
|
+
return el('div', { class: 'rp-mywork-row td-day__r' + (open ? ' rp-mywork-row--open' : ''), role: 'listitem', 'data-my-work-item': x.id, 'data-cur': open ? 'true' : null },
|
|
132
151
|
row({
|
|
133
152
|
title: x.title,
|
|
134
153
|
meta,
|
|
135
|
-
lead: [
|
|
136
|
-
trail: [x
|
|
154
|
+
lead: [statusDot(x.wsjf.tier)],
|
|
155
|
+
trail: [...trailingBadges(x), x.state !== 'brief' ? stateIcon(x) : el('span', { 'data-my-work-state': x.state, hidden: '' })].filter(Boolean),
|
|
137
156
|
onOpen: () => { inst.open = x.id; inst.details = false; inst.last = ''; draw(inst); },
|
|
138
157
|
openLabel: `${x.title}, ${x.wsjf.tier}, ${stateWord(x)}`,
|
|
139
158
|
openAttrs: { 'aria-current': open ? 'true' : null, 'data-my-work-open': x.id },
|
|
@@ -143,14 +162,19 @@ function itemRow(inst, x) {
|
|
|
143
162
|
function listPane(inst, d) {
|
|
144
163
|
const shown = d.items.filter((x) => TAB_STATES[inst.tab].includes(x.state));
|
|
145
164
|
const counts = { brief: d.counts.brief, agents: d.counts.agents, ready: d.counts.ready, all: d.counts.all };
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
165
|
+
const panelChildren = [
|
|
166
|
+
el('h2', { class: 'td-day__h2' }, COPY.tabs[inst.tab], el('span', null, counts[inst.tab] + (counts[inst.tab] === 1 ? ' item' : ' items')))
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
if (shown.length) {
|
|
170
|
+
const rowsContainer = el('div', { role: 'list', 'data-my-work-list': inst.tab });
|
|
171
|
+
shown.forEach((x) => rowsContainer.appendChild(itemRow(inst, x)));
|
|
172
|
+
panelChildren.push(rowsContainer);
|
|
173
|
+
} else {
|
|
174
|
+
panelChildren.push(el('p', { class: 'rp-section__empty' }, COPY.none[inst.tab]));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return el('section', { class: 'td-day__panel' }, ...panelChildren);
|
|
154
178
|
}
|
|
155
179
|
|
|
156
180
|
/* ── the item ────────────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
@@ -178,7 +202,7 @@ function briefSec(inst, x) {
|
|
|
178
202
|
const r = await act(inst, { action: 'brief', item: x.id, instruction }, COPY.started);
|
|
179
203
|
if (r) inst.drafts.delete(x.id);
|
|
180
204
|
} });
|
|
181
|
-
const mine = button({ label: COPY.handle, variant: 'outline', size: 'sm', disabled: inst.busy, attrs: { 'data-my-work-act': 'handle' },
|
|
205
|
+
const mine = button({ label: COPY.handle, variant: 'outline', size: 'sm', icon: 'check', disabled: inst.busy, attrs: { 'data-my-work-act': 'handle' },
|
|
182
206
|
onClick: () => act(inst, { action: 'handle', item: x.id }, COPY.taken) });
|
|
183
207
|
return el('div', { class: 'rp-mywork-brief' }, f, el('div', { class: 'rp-mywork-acts' }, run, mine));
|
|
184
208
|
}
|
|
@@ -198,14 +222,18 @@ function statusSec(inst, x) {
|
|
|
198
222
|
|
|
199
223
|
function itemPane(inst, d) {
|
|
200
224
|
const x = d.items.find((y) => y.id === inst.open);
|
|
201
|
-
if (!x) return el('
|
|
225
|
+
if (!x) return el('section', { class: 'td-day__detail' }, el('p', { class: 'rp-section__empty' }, COPY.pick));
|
|
226
|
+
|
|
202
227
|
const head = el('header', { class: 'rp-mywork-who' },
|
|
203
228
|
el('h3', { class: 'rp-mywork-who__title' }, x.title),
|
|
204
229
|
el('p', { class: 'rp-mywork-hint' }, [x.source === 'jira' ? 'Jira' : 'WBS', x.key, x.repo, x.status, x.due ? COPY.due(x.due) : null, x.from ? COPY.from(x.from) : null].filter(Boolean).join(' · ')),
|
|
205
230
|
x.url ? el('a', { class: 'rp-mywork-link', href: x.url, target: '_blank', rel: 'noopener noreferrer' }, icon('link'), ' ', COPY.source) : null);
|
|
231
|
+
|
|
206
232
|
const details = button({ label: inst.details ? COPY.hide : COPY.details, variant: 'ghost', size: 'sm', iconEnd: inst.details ? 'chevron-down' : 'chevron-right',
|
|
207
233
|
attrs: { 'aria-expanded': inst.details ? 'true' : 'false', 'data-my-work-details': '' }, onClick: () => { inst.details = !inst.details; inst.last = ''; draw(inst); } });
|
|
208
|
-
|
|
234
|
+
|
|
235
|
+
return el('article', { class: 'rp-mywork-page td-day__detail', 'data-my-work-page': x.id },
|
|
236
|
+
el('h2', { class: 'td-day__h2' }, 'Brief', el('span', null, x.key)),
|
|
209
237
|
head, foundSec(x),
|
|
210
238
|
x.state === 'brief' ? section(COPY.instruction, [briefSec(inst, x)], { 'data-my-work-sec': 'brief' }) : statusSec(inst, x),
|
|
211
239
|
el('div', { class: 'rp-mywork-acts' }, details), inst.details ? el('p', { class: 'rp-mywork-hint', 'data-my-work-score': '' }, COPY.score(x.wsjf)) : null);
|
|
@@ -213,18 +241,42 @@ function itemPane(inst, d) {
|
|
|
213
241
|
|
|
214
242
|
/* ── the page ────────────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
215
243
|
|
|
216
|
-
function header(inst) {
|
|
217
|
-
|
|
244
|
+
function header(inst, d) {
|
|
245
|
+
const key = JSON.stringify([d && d.counts, inst.tab]);
|
|
246
|
+
if (inst.head && inst.headData === key) return inst.head;
|
|
247
|
+
inst.headData = key;
|
|
248
|
+
|
|
249
|
+
const counts = d && d.counts ? d.counts : { brief: 0, agents: 0, ready: 0, all: 0 };
|
|
250
|
+
const briefCount = counts.brief || 0;
|
|
251
|
+
const nounPhrase = briefCount === 0 ? 'Nothing to brief' : `${briefCount} to brief`;
|
|
252
|
+
|
|
253
|
+
const segmentedTabs = tabs({ variant: 'segmented', role: 'radiogroup', value: inst.tab, ariaLabel: 'Filter', attrs: { 'data-my-work-tabs': '' },
|
|
254
|
+
items: Object.keys(COPY.tabs).map((id) => ({ id, label: COPY.tabs[id], count: counts[id] })),
|
|
255
|
+
onChange: (v) => { inst.tab = v; inst.open = null; inst.last = ''; draw(inst); } });
|
|
256
|
+
const btns = segmentedTabs.querySelectorAll('button');
|
|
257
|
+
Object.keys(COPY.tabs).forEach((id, i) => { if (btns[i]) btns[i].setAttribute('data-tab', id); });
|
|
258
|
+
|
|
218
259
|
inst.readLine = el('p', { class: 'rp-mywork-hint', 'data-my-work-read': '' });
|
|
219
|
-
|
|
220
|
-
|
|
260
|
+
const readAgainBtn = button({ label: COPY.readAgain, variant: 'outline', size: 'sm', icon: 'refresh-cw', attrs: { 'data-my-work-act': 'refresh', 'data-my-work-read': '' }, onClick: () => readAgain(inst) });
|
|
261
|
+
|
|
262
|
+
const headerRow = el('div', { class: 'rp-mywork-header' },
|
|
263
|
+
el('h2', { class: 'rp-mywork-title' }, nounPhrase),
|
|
264
|
+
segmentedTabs);
|
|
265
|
+
|
|
266
|
+
const stripCard = el('div', { class: 'rp-mywork-strip' },
|
|
267
|
+
inst.readLine,
|
|
268
|
+
el('span', { class: 'rp-mywork-spacer' }),
|
|
269
|
+
inst.countLine = el('span', { class: 'rp-mywork-hint' }),
|
|
270
|
+
readAgainBtn);
|
|
271
|
+
|
|
272
|
+
inst.head = el('div', { class: 'rp-mywork-top' }, headerRow, stripCard);
|
|
221
273
|
return inst.head;
|
|
222
274
|
}
|
|
223
275
|
|
|
224
276
|
function draw(inst) {
|
|
225
277
|
const root = inst.root;
|
|
226
278
|
const d = myWork();
|
|
227
|
-
const head = header(inst);
|
|
279
|
+
const head = header(inst, d);
|
|
228
280
|
if (head.parentNode !== root || inst.body.parentNode !== root) root.replaceChildren(head, inst.body);
|
|
229
281
|
const fp = JSON.stringify([d, inst.tab, inst.open, inst.details, inst.busy]);
|
|
230
282
|
if (fp === inst.last) return;
|
|
@@ -235,8 +287,14 @@ function draw(inst) {
|
|
|
235
287
|
if (!d) { inst.body.replaceChildren(el('p', { class: 'rp-section__empty' }, COPY.loading)); return; }
|
|
236
288
|
if (!d.ok) { inst.body.replaceChildren(el('p', { class: 'rp-section__empty rp-section__empty--bad', role: 'alert' }, d.error || COPY.readError)); return; }
|
|
237
289
|
inst.readLine.textContent = COPY.readAt(d.read_at ? new Date(d.read_at).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : null);
|
|
290
|
+
const nJira = d.items.filter((x) => x.source === 'jira').length;
|
|
291
|
+
const nPlan = d.items.length - nJira;
|
|
292
|
+
inst.countLine.textContent = `${nJira} ticket${nJira === 1 ? '' : 's'} · ${nPlan} plan item${nPlan === 1 ? '' : 's'}`;
|
|
238
293
|
const shown = d.items.filter((x) => TAB_STATES[inst.tab].includes(x.state));
|
|
239
294
|
if (!shown.some((x) => x.id === inst.open)) inst.open = shown.length ? shown[0].id : null;
|
|
240
295
|
const banner = (d.banner || []).length ? el('p', { class: 'rp-mywork-hint', role: 'status', 'data-my-work-banner': '' }, d.banner.map((b) => b.source + ': ' + b.error).join(' · ')) : null;
|
|
241
|
-
|
|
296
|
+
const grid = el('div', { class: 'rp-mywork-grid td-day__cols' },
|
|
297
|
+
listPane(inst, d),
|
|
298
|
+
itemPane(inst, d));
|
|
299
|
+
inst.body.replaceChildren(...[banner, grid].filter(Boolean));
|
|
242
300
|
}
|
|
@@ -17,6 +17,10 @@ export const COPY = {
|
|
|
17
17
|
command: 'Command', destination: 'Go to', initiative: 'Initiative', repository: 'Repository', session: 'Session',
|
|
18
18
|
tab: 'Tab', view: 'View', knowledge: 'Document', memory: 'Memory', guide: 'Guide', capture: 'Note', ask: 'Ask',
|
|
19
19
|
},
|
|
20
|
+
groups: {
|
|
21
|
+
destination: 'Go to', command: 'Commands', initiative: 'Initiatives', repository: 'Repositories', session: 'Sessions',
|
|
22
|
+
tab: 'Tabs', view: 'Views', knowledge: 'Knowledge', memory: 'Memory', guide: 'Guide', capture: 'Keep', ask: 'Keep',
|
|
23
|
+
},
|
|
20
24
|
hint: 'Type to search. Press Enter to open the first row.',
|
|
21
25
|
hintTyped: 'Type to search. Capture as note keeps what you typed.',
|
|
22
26
|
noMatch: (q) => `Nothing matches "${q}".`,
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
|
|
22
22
|
import { el } from './dom.js';
|
|
23
23
|
import { state, setState, FIXTURE } from './state.js';
|
|
24
|
-
import { sheet, field, tabs, row, badge, signal, toast, button, dialogText, ensureComponentsCss } from './primitives.js';
|
|
25
|
-
import { ensureSprite } from './icons.js';
|
|
24
|
+
import { sheet, field, tabs, row, badge, signal, toast, button, dialogText, ensureComponentsCss, narrowViewport } from './primitives.js';
|
|
25
|
+
import { ensureSprite, icon } from './icons.js';
|
|
26
26
|
import { pmGet, pmPost } from './http.js';
|
|
27
27
|
import { COPY } from './palette-copy.js';
|
|
28
28
|
|
|
@@ -215,15 +215,27 @@ function choose(it) {
|
|
|
215
215
|
try { it.run(); } catch (e) { console.warn('[palette] a row failed:', (e && e.message) || e); }
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
/** Get the kind icon for a palette item. */
|
|
219
|
+
function kindIcon(kind) {
|
|
220
|
+
const iconMap = {
|
|
221
|
+
destination: 'arrow-right', command: 'code', initiative: 'target', repository: 'git-branch', session: 'square-terminal',
|
|
222
|
+
tab: 'panel-top', view: 'panel-top', knowledge: 'file-text', memory: 'history', guide: 'list', capture: 'text-quote', ask: 'bot',
|
|
223
|
+
};
|
|
224
|
+
return iconMap[kind] || 'circle';
|
|
225
|
+
}
|
|
226
|
+
|
|
218
227
|
function itemRow(it, i, id) {
|
|
219
228
|
const sigs = [];
|
|
220
229
|
if (it.cli) sigs.push(el('code', { class: 'pl-cli' }, it.cli));
|
|
221
230
|
else if (it.tag) sigs.push(signal({ text: it.tag }));
|
|
231
|
+
const lead = [el('span', { class: 'pl-icon-tile', 'aria-hidden': 'true' }, icon(kindIcon(it.kind)))];
|
|
232
|
+
const trail = i === sel ? [el('span', { class: 'pl-enter-icon', 'aria-hidden': 'true' }, icon('chevron-right'))] : [];
|
|
222
233
|
return row({
|
|
223
234
|
title: it.label,
|
|
224
235
|
meta: [it.hint].filter(Boolean),
|
|
225
236
|
signals: sigs,
|
|
226
|
-
lead
|
|
237
|
+
lead,
|
|
238
|
+
trail,
|
|
227
239
|
onOpen: () => choose(it),
|
|
228
240
|
openAttrs: { 'data-palette': 'item', 'data-kind': it.kind, tabindex: '-1' },
|
|
229
241
|
attrs: { class: 'pl-item', id, role: 'option', 'aria-selected': i === sel ? 'true' : 'false' },
|
|
@@ -238,8 +250,21 @@ function refresh() {
|
|
|
238
250
|
const sig = rows.map((it) => `${it.kind}|${it.label}|${it.hint || ''}|${it.cli || ''}`).join('\n') + '#' + sel;
|
|
239
251
|
if (sig !== ui.sig) {
|
|
240
252
|
ui.sig = sig;
|
|
241
|
-
|
|
242
|
-
const
|
|
253
|
+
// Build the list with eyebrow labels for groups
|
|
254
|
+
const grouped = [];
|
|
255
|
+
let lastKind = null;
|
|
256
|
+
let itemCount = 0;
|
|
257
|
+
for (const it of rows) {
|
|
258
|
+
if (it.kind !== lastKind) {
|
|
259
|
+
const groupName = COPY.groups[it.kind] || COPY.kinds[it.kind] || it.kind;
|
|
260
|
+
grouped.push(el('div', { class: 'pl-eyebrow', 'aria-hidden': 'true' }, groupName));
|
|
261
|
+
lastKind = it.kind;
|
|
262
|
+
}
|
|
263
|
+
grouped.push(itemRow(it, itemCount, ui.listId + '-' + itemCount));
|
|
264
|
+
itemCount++;
|
|
265
|
+
}
|
|
266
|
+
ui.list.replaceChildren(...grouped);
|
|
267
|
+
const cur = ui.list.querySelector('[role="option"][aria-selected="true"]');
|
|
243
268
|
ui.input.control.setAttribute('aria-activedescendant', cur ? cur.id : '');
|
|
244
269
|
if (cur && cur.scrollIntoView) cur.scrollIntoView({ block: 'nearest' });
|
|
245
270
|
}
|
|
@@ -262,7 +287,7 @@ function setScope(next) {
|
|
|
262
287
|
ui.input.control.focus();
|
|
263
288
|
}
|
|
264
289
|
|
|
265
|
-
const scopeTabs = () => tabs({ variant: 'line', items: SCOPES.map((id) => ({ id, label: COPY.scopes[id] })), value: scope, onChange: setScope, ariaLabel: COPY.scopeLabel, attrs: { 'data-palette': 'scopes' } });
|
|
290
|
+
const scopeTabs = () => tabs({ variant: narrowViewport() ? 'line' : 'segmented', items: SCOPES.map((id) => ({ id, label: COPY.scopes[id] })), value: scope, onChange: setScope, ariaLabel: COPY.scopeLabel, attrs: { 'data-palette': 'scopes', class: 'pl-scopes' } });
|
|
266
291
|
|
|
267
292
|
function onKey(e) {
|
|
268
293
|
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
@@ -282,11 +307,17 @@ function openPalette(deps) {
|
|
|
282
307
|
q = ''; scope = 'all'; sel = 0; noteKey = ''; pinned = null; busy = false;
|
|
283
308
|
const listId = 'pl-list-' + Math.random().toString(36).slice(2, 7);
|
|
284
309
|
const input = field({ label: COPY.inputLabel, hideLabel: true, placeholder: COPY.placeholder,
|
|
285
|
-
attrs: { 'data-palette': 'input', role: 'combobox', 'aria-expanded': 'true', 'aria-controls': listId, 'aria-autocomplete': 'list', spellcheck: 'false' } });
|
|
310
|
+
attrs: { 'data-palette': 'input', role: 'combobox', 'aria-expanded': 'true', 'aria-controls': listId, 'aria-autocomplete': 'list', spellcheck: 'false', class: narrowViewport() ? '' : 'pl-input' } });
|
|
286
311
|
const list = el('div', { class: 'pl-list', role: 'listbox', id: listId, 'aria-label': COPY.title, 'data-palette': 'list' });
|
|
287
312
|
const note = el('p', { class: 'pl-note', role: 'status', 'data-palette': 'note' });
|
|
288
313
|
const scopes = scopeTabs();
|
|
289
|
-
|
|
314
|
+
|
|
315
|
+
// Center mode for palette on desktop, bottom sheet on phone
|
|
316
|
+
const mode = narrowViewport() ? 'bottom' : 'center';
|
|
317
|
+
const center = mode === 'center';
|
|
318
|
+
const search = center ? el('div', { class: 'pl-search' }, el('span', { class: 'pl-search__icon', 'aria-hidden': 'true' }, icon('search')), input, el('kbd', { class: 'pl-kbd', 'aria-hidden': 'true' }, 'Esc')) : input;
|
|
319
|
+
const foot = center ? el('div', { class: 'pl-foot' }, note, el('span', { class: 'pl-keys' }, COPY.keys)) : note;
|
|
320
|
+
const dlg = sheet({ mode, title: COPY.title, sub: COPY.sub, body: [search, scopes, list, foot], attrs: { 'data-palette': 'overlay', class: center ? 'pl-center' : '' },
|
|
290
321
|
onClose: () => { ui = null; if (state.paletteOpen) setState({ paletteOpen: false }); } });
|
|
291
322
|
ui = { sheet: dlg, input, list, note, scopes, deps, sig: null, listId };
|
|
292
323
|
input.control.addEventListener('input', () => { input.control.resetCommitted(input.control.value); q = input.control.value; sel = 0; pinned = null; if (ui) { ui.sig = null; refresh(); } });
|
|
@@ -394,21 +394,23 @@ export function narrowViewport() {
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
/**
|
|
397
|
-
* sheet({ mode: 'auto'|'side'|'bottom', title, sub, body: Node[], actions: Node[], onClose, attrs })
|
|
398
|
-
* A modal <dialog> in the layer: 420px at the side above 760px, a bottom sheet below. Escape and the backdrop close it.
|
|
397
|
+
* sheet({ mode: 'auto'|'side'|'bottom'|'center', title, sub, body: Node[], actions: Node[], onClose, attrs })
|
|
398
|
+
* A modal <dialog> in the layer: 420px at the side above 760px, a bottom sheet below, or centered. Escape and the backdrop close it.
|
|
399
399
|
* On a phone the sheet follows the on-screen keyboard through visualViewport. Returns { dialog, body, close, setHead }.
|
|
400
400
|
*/
|
|
401
401
|
export function sheet({ mode = 'auto', title, sub, body, actions, onClose, attrs } = {}) {
|
|
402
402
|
const m = mode === 'auto' ? (narrowViewport() ? 'bottom' : 'side') : mode;
|
|
403
403
|
const tid = nextId('pmc-sheet-t');
|
|
404
|
-
const titleEl = el('h2', { class: 'pmc-sheet__title', id: tid }, title || '');
|
|
405
|
-
const subEl = el('div', { class: 'pmc-sheet__sub' }, sub || '');
|
|
404
|
+
const titleEl = el('h2', { class: cx('pmc-sheet__title', m === 'center' && 'pmc-sheet__title--hidden'), id: tid }, title || '');
|
|
405
|
+
const subEl = el('div', { class: cx('pmc-sheet__sub', m === 'center' && 'pmc-sheet__sub--hidden') }, sub || '');
|
|
406
406
|
const bodyEl = el('div', { class: 'pmc-sheet__body' }, body || []);
|
|
407
407
|
let closed = false;
|
|
408
|
+
const headEl = m === 'center' ? null : el('div', { class: 'pmc-sheet__head' }, el('div', { class: 'pmc-sheet__heading' }, titleEl, subEl),
|
|
409
|
+
iconButton({ icon: 'x', ariaLabel: 'Close', onClick: () => close() }));
|
|
408
410
|
const dialog = el('dialog', { 'aria-modal': 'true', 'aria-labelledby': tid, tabindex: '-1', ...withClass(cx('pmc-sheet', 'pmc-sheet--' + m), attrs) },
|
|
409
411
|
m === 'bottom' ? el('div', { class: 'pmc-sheet__handle', 'aria-hidden': 'true' }) : null,
|
|
410
|
-
|
|
411
|
-
|
|
412
|
+
m === 'center' ? titleEl : headEl,
|
|
413
|
+
m === 'center' ? subEl : null,
|
|
412
414
|
bodyEl,
|
|
413
415
|
actions && actions.length ? el('div', { class: 'pmc-sheet__actions' }, actions) : null);
|
|
414
416
|
|