Spaces:
Paused
Paused
Pi Web fork: Copy only on answers, one step rhythm, no letter spacing
Browse files- piweb/hf-design.patch +352 -215
piweb/hf-design.patch
CHANGED
|
@@ -3977,7 +3977,7 @@ index 0000000..8d003fe
|
|
| 3977 |
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
| 3978 |
+OTHER DEALINGS IN THE FONT SOFTWARE.
|
| 3979 |
diff --git a/app/globals.css b/app/globals.css
|
| 3980 |
-
index d6b5535..
|
| 3981 |
--- a/app/globals.css
|
| 3982 |
+++ b/app/globals.css
|
| 3983 |
@@ -24,46 +24,109 @@
|
|
@@ -4235,7 +4235,7 @@ index d6b5535..9f01813 100644
|
|
| 4235 |
}
|
| 4236 |
|
| 4237 |
/* Theme toggle: circular wipe via View Transitions API.
|
| 4238 |
-
@@ -218,
|
| 4239 |
margin-bottom: 22px;
|
| 4240 |
}
|
| 4241 |
|
|
@@ -4247,7 +4247,14 @@ index d6b5535..9f01813 100644
|
|
| 4247 |
}
|
| 4248 |
|
| 4249 |
.web-login-brand h1 {
|
| 4250 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4251 |
min-width: 0;
|
| 4252 |
padding: 10px 10px 10px 14px;
|
| 4253 |
border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
|
|
@@ -4266,7 +4273,7 @@ index d6b5535..9f01813 100644
|
|
| 4266 |
}
|
| 4267 |
|
| 4268 |
.web-login-label {
|
| 4269 |
-
@@ -307,
|
| 4270 |
min-height: 32px;
|
| 4271 |
padding: 7px 14px;
|
| 4272 |
border: none;
|
|
@@ -4275,7 +4282,14 @@ index d6b5535..9f01813 100644
|
|
| 4275 |
background: var(--accent);
|
| 4276 |
color: var(--accent-contrast);
|
| 4277 |
cursor: pointer;
|
| 4278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4279 |
}
|
| 4280 |
|
| 4281 |
.web-login-form button:focus-visible {
|
|
@@ -4284,7 +4298,7 @@ index d6b5535..9f01813 100644
|
|
| 4284 |
outline-offset: 2px;
|
| 4285 |
}
|
| 4286 |
|
| 4287 |
-
@@ -332,110 +
|
| 4288 |
cursor: not-allowed;
|
| 4289 |
}
|
| 4290 |
|
|
@@ -4380,7 +4394,7 @@ index d6b5535..9f01813 100644
|
|
| 4380 |
overflow: hidden;
|
| 4381 |
- text-overflow: ellipsis;
|
| 4382 |
- white-space: nowrap;
|
| 4383 |
-
|
| 4384 |
-
|
| 4385 |
-.terminal-status-dot {
|
| 4386 |
- width: 7px;
|
|
@@ -4407,7 +4421,7 @@ index d6b5535..9f01813 100644
|
|
| 4407 |
- color: #9ca3af;
|
| 4408 |
- cursor: pointer;
|
| 4409 |
- font: inherit;
|
| 4410 |
-
|
| 4411 |
-
|
| 4412 |
-.terminal-panel-exit {
|
| 4413 |
- padding: 7px 12px;
|
|
@@ -4505,7 +4519,7 @@ index d6b5535..9f01813 100644
|
|
| 4505 |
}
|
| 4506 |
|
| 4507 |
.terminal-xterm:has(+ .terminal-extra-keys) {
|
| 4508 |
-
@@ -451,7 +
|
| 4509 |
padding: 0;
|
| 4510 |
border: none;
|
| 4511 |
background: transparent;
|
|
@@ -4514,7 +4528,7 @@ index d6b5535..9f01813 100644
|
|
| 4514 |
font-family: var(--font-mono);
|
| 4515 |
font-size: 12px;
|
| 4516 |
touch-action: manipulation;
|
| 4517 |
-
@@ -462,7 +
|
| 4518 |
}
|
| 4519 |
|
| 4520 |
.terminal-extra-keys button:active {
|
|
@@ -4523,7 +4537,7 @@ index d6b5535..9f01813 100644
|
|
| 4523 |
}
|
| 4524 |
|
| 4525 |
.terminal-extra-keys .is-rotated {
|
| 4526 |
-
@@ -471,17 +
|
| 4527 |
}
|
| 4528 |
|
| 4529 |
.terminal-extra-keys button.is-active {
|
|
@@ -4544,7 +4558,7 @@ index d6b5535..9f01813 100644
|
|
| 4544 |
|
| 4545 |
/*
|
| 4546 |
* Chromium-only scrollbars. Firefox renders its native ones, which the standard
|
| 4547 |
-
@@ -502,7 +
|
| 4548 |
background-color: color-mix(in srgb, var(--text-dim) 45%, transparent);
|
| 4549 |
background-clip: padding-box;
|
| 4550 |
border: 2px solid transparent;
|
|
@@ -4553,7 +4567,7 @@ index d6b5535..9f01813 100644
|
|
| 4553 |
}
|
| 4554 |
/* Only ever set background-color here: the `background` shorthand resets
|
| 4555 |
background-clip and would snap the thumb to the full 10px on hover. */
|
| 4556 |
-
@@ -538,21 +
|
| 4557 |
}
|
| 4558 |
::-webkit-scrollbar-thumb {
|
| 4559 |
border-width: 1px;
|
|
@@ -4576,7 +4590,7 @@ index d6b5535..9f01813 100644
|
|
| 4576 |
|
| 4577 |
|
| 4578 |
.extension-status-shelf {
|
| 4579 |
-
@@ -686,8 +
|
| 4580 |
}
|
| 4581 |
|
| 4582 |
button.extension-widget-trigger:focus-visible {
|
|
@@ -4588,7 +4602,7 @@ index d6b5535..9f01813 100644
|
|
| 4588 |
}
|
| 4589 |
|
| 4590 |
.extension-widget-placement {
|
| 4591 |
-
@@ -703,9 +
|
| 4592 |
|
| 4593 |
.extension-widget-placement-icon {
|
| 4594 |
display: block;
|
|
@@ -4601,7 +4615,7 @@ index d6b5535..9f01813 100644
|
|
| 4601 |
fill: currentColor;
|
| 4602 |
}
|
| 4603 |
|
| 4604 |
-
@@ -803,8 +
|
| 4605 |
}
|
| 4606 |
|
| 4607 |
.extension-status-command:focus-visible {
|
|
@@ -4613,7 +4627,7 @@ index d6b5535..9f01813 100644
|
|
| 4613 |
}
|
| 4614 |
|
| 4615 |
.extension-status-command:disabled {
|
| 4616 |
-
@@ -826,8 +
|
| 4617 |
}
|
| 4618 |
|
| 4619 |
:root {
|
|
@@ -4624,7 +4638,15 @@ index d6b5535..9f01813 100644
|
|
| 4624 |
--font-ui: var(--font-ui-default);
|
| 4625 |
--font-mono: var(--font-mono-default);
|
| 4626 |
--font-ui-weight: 400;
|
| 4627 |
-
@@ -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4628 |
.markdown-body ol > li { list-style-type: decimal; }
|
| 4629 |
.markdown-body li > p { margin: 0; }
|
| 4630 |
.markdown-body li::marker {
|
|
@@ -4639,7 +4661,7 @@ index d6b5535..9f01813 100644
|
|
| 4639 |
font-weight: 700;
|
| 4640 |
}
|
| 4641 |
.markdown-body em { color: var(--text-muted); }
|
| 4642 |
-
@@ -896,24 +
|
| 4643 |
text-decoration: underline;
|
| 4644 |
text-decoration-thickness: 1px;
|
| 4645 |
text-underline-offset: 3px;
|
|
@@ -4668,7 +4690,7 @@ index d6b5535..9f01813 100644
|
|
| 4668 |
}
|
| 4669 |
.markdown-body .contains-task-list {
|
| 4670 |
padding-left: 0;
|
| 4671 |
-
@@ -935,7 +
|
| 4672 |
height: 14px;
|
| 4673 |
margin: 0;
|
| 4674 |
border: 1px solid var(--border);
|
|
@@ -4677,7 +4699,7 @@ index d6b5535..9f01813 100644
|
|
| 4677 |
background: var(--bg);
|
| 4678 |
display: inline-grid;
|
| 4679 |
place-content: center;
|
| 4680 |
-
@@ -967,8 +
|
| 4681 |
margin: 8px 0;
|
| 4682 |
overflow-x: auto;
|
| 4683 |
overflow-y: hidden;
|
|
@@ -4688,7 +4710,7 @@ index d6b5535..9f01813 100644
|
|
| 4688 |
}
|
| 4689 |
.markdown-body table {
|
| 4690 |
border-collapse: separate;
|
| 4691 |
-
@@ -1088,7 +
|
| 4692 |
padding: 7px 8px;
|
| 4693 |
overflow: auto;
|
| 4694 |
border: 1px solid var(--border);
|
|
@@ -4697,7 +4719,7 @@ index d6b5535..9f01813 100644
|
|
| 4697 |
background: var(--bg-panel);
|
| 4698 |
font-family: var(--font-mono);
|
| 4699 |
font-size: 11px;
|
| 4700 |
-
@@ -1108,16 +
|
| 4701 |
min-width: 0;
|
| 4702 |
max-width: 100%;
|
| 4703 |
margin: 6px 0;
|
|
@@ -4717,7 +4739,7 @@ index d6b5535..9f01813 100644
|
|
| 4717 |
color: var(--text-dim);
|
| 4718 |
font-size: 11px;
|
| 4719 |
display: flex;
|
| 4720 |
-
@@ -1137,7 +
|
| 4721 |
}
|
| 4722 |
.markdown-code-action {
|
| 4723 |
border: 1px solid var(--border);
|
|
@@ -4726,7 +4748,7 @@ index d6b5535..9f01813 100644
|
|
| 4726 |
padding: 2px 7px;
|
| 4727 |
background: var(--bg);
|
| 4728 |
color: var(--text-muted);
|
| 4729 |
-
@@ -1191,8 +
|
| 4730 |
opacity: 0.85;
|
| 4731 |
}
|
| 4732 |
.mermaid-preview-button:focus-visible {
|
|
@@ -4738,7 +4760,7 @@ index d6b5535..9f01813 100644
|
|
| 4738 |
}
|
| 4739 |
|
| 4740 |
.image-preview-dialog {
|
| 4741 |
-
@@ -1228,7 +
|
| 4742 |
max-width: 100%;
|
| 4743 |
max-height: 100%;
|
| 4744 |
object-fit: contain;
|
|
@@ -4747,7 +4769,7 @@ index d6b5535..9f01813 100644
|
|
| 4747 |
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
|
| 4748 |
}
|
| 4749 |
|
| 4750 |
-
@@ -1244,7 +
|
| 4751 |
height: 36px;
|
| 4752 |
padding: 0;
|
| 4753 |
border: 1px solid var(--border);
|
|
@@ -4756,7 +4778,7 @@ index d6b5535..9f01813 100644
|
|
| 4756 |
background: var(--bg-panel);
|
| 4757 |
color: var(--text-muted);
|
| 4758 |
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.28);
|
| 4759 |
-
@@ -1259,7 +
|
| 4760 |
}
|
| 4761 |
|
| 4762 |
.image-preview-close:focus-visible {
|
|
@@ -4765,7 +4787,7 @@ index d6b5535..9f01813 100644
|
|
| 4765 |
outline-offset: 2px;
|
| 4766 |
}
|
| 4767 |
|
| 4768 |
-
@@ -1320,7 +
|
| 4769 |
height: 24px;
|
| 4770 |
overflow: hidden;
|
| 4771 |
border: 1px solid var(--border);
|
|
@@ -4774,7 +4796,7 @@ index d6b5535..9f01813 100644
|
|
| 4774 |
background: var(--bg);
|
| 4775 |
}
|
| 4776 |
.mermaid-zoom-stepper button,
|
| 4777 |
-
@@ -1347,7 +
|
| 4778 |
.mermaid-zoom-icon-button {
|
| 4779 |
flex: 0 0 24px;
|
| 4780 |
border: 1px solid var(--border);
|
|
@@ -4783,7 +4805,7 @@ index d6b5535..9f01813 100644
|
|
| 4783 |
}
|
| 4784 |
.mermaid-zoom-stepper button:hover:not(:disabled),
|
| 4785 |
.mermaid-zoom-icon-button:hover {
|
| 4786 |
-
@@ -1360,8 +
|
| 4787 |
}
|
| 4788 |
.mermaid-zoom-stepper button:focus-visible,
|
| 4789 |
.mermaid-zoom-icon-button:focus-visible {
|
|
@@ -4795,7 +4817,7 @@ index d6b5535..9f01813 100644
|
|
| 4795 |
}
|
| 4796 |
.mermaid-zoom-value {
|
| 4797 |
min-width: 48px;
|
| 4798 |
-
@@ -1384,7 +
|
| 4799 |
.mermaid-zoom-canvas {
|
| 4800 |
padding: 12px;
|
| 4801 |
border: 1px solid var(--border);
|
|
@@ -4804,7 +4826,7 @@ index d6b5535..9f01813 100644
|
|
| 4804 |
background: var(--bg-panel);
|
| 4805 |
margin: 0 auto;
|
| 4806 |
transition: width 0.15s ease;
|
| 4807 |
-
@@ -1460,7 +
|
| 4808 |
height: 24px;
|
| 4809 |
overflow: hidden;
|
| 4810 |
border: 1px solid var(--border);
|
|
@@ -4813,7 +4835,15 @@ index d6b5535..9f01813 100644
|
|
| 4813 |
}
|
| 4814 |
|
| 4815 |
.file-viewer-mode-button {
|
| 4816 |
-
@@ -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4817 |
height: 32px;
|
| 4818 |
padding: 0;
|
| 4819 |
box-sizing: border-box;
|
|
@@ -4828,7 +4858,7 @@ index d6b5535..9f01813 100644
|
|
| 4828 |
opacity: 0;
|
| 4829 |
visibility: hidden;
|
| 4830 |
pointer-events: none;
|
| 4831 |
-
@@ -1542,7 +
|
| 4832 |
}
|
| 4833 |
|
| 4834 |
.chat-scroll-to-bottom:focus-visible {
|
|
@@ -4837,7 +4867,7 @@ index d6b5535..9f01813 100644
|
|
| 4837 |
outline-offset: 2px;
|
| 4838 |
}
|
| 4839 |
|
| 4840 |
-
@@ -1566,11 +
|
| 4841 |
}
|
| 4842 |
}
|
| 4843 |
|
|
@@ -4855,7 +4885,7 @@ index d6b5535..9f01813 100644
|
|
| 4855 |
margin: 0 auto;
|
| 4856 |
}
|
| 4857 |
|
| 4858 |
-
@@ -1584,22 +
|
| 4859 |
|
| 4860 |
.new-session-logo {
|
| 4861 |
flex: none;
|
|
@@ -4880,11 +4910,10 @@ index d6b5535..9f01813 100644
|
|
| 4880 |
+ color: var(--text-strong);
|
| 4881 |
+ font-size: 20px;
|
| 4882 |
+ font-weight: 600;
|
| 4883 |
-
+ letter-spacing: -0.02em;
|
| 4884 |
line-height: 1.15;
|
| 4885 |
white-space: nowrap;
|
| 4886 |
}
|
| 4887 |
-
@@ -1613,7 +
|
| 4888 |
gap: 6px;
|
| 4889 |
min-width: 0;
|
| 4890 |
color: var(--text-dim);
|
|
@@ -4894,7 +4923,7 @@ index d6b5535..9f01813 100644
|
|
| 4894 |
line-height: 1.4;
|
| 4895 |
white-space: nowrap;
|
| 4896 |
}
|
| 4897 |
-
@@ -1638,7 +
|
| 4898 |
gap: 3px;
|
| 4899 |
margin: -3px -4px;
|
| 4900 |
padding: 3px 4px;
|
|
@@ -4903,7 +4932,7 @@ index d6b5535..9f01813 100644
|
|
| 4903 |
color: var(--accent);
|
| 4904 |
font-weight: 600;
|
| 4905 |
text-decoration: none;
|
| 4906 |
-
@@ -1652,38 +
|
| 4907 |
|
| 4908 |
/* The ring inside the box, clear of the "→" and "·" beside it. */
|
| 4909 |
.new-session-update:focus-visible {
|
|
@@ -4948,7 +4977,7 @@ index d6b5535..9f01813 100644
|
|
| 4948 |
}
|
| 4949 |
}
|
| 4950 |
|
| 4951 |
-
@@ -1700,8 +
|
| 4952 |
tab's stacked boxes, the row below the sessions), as the sidebar drew it
|
| 4953 |
on main: two grey boxes in the header buttons' material, 11px, the
|
| 4954 |
project's whole path and the worktree's branch in code type, cut at their
|
|
@@ -4959,7 +4988,7 @@ index d6b5535..9f01813 100644
|
|
| 4959 |
.project-picker {
|
| 4960 |
display: flex;
|
| 4961 |
align-items: center;
|
| 4962 |
-
@@ -1722,52 +
|
| 4963 |
min-width: 0;
|
| 4964 |
height: 29px;
|
| 4965 |
padding: 0 10px;
|
|
@@ -5015,7 +5044,7 @@ index d6b5535..9f01813 100644
|
|
| 5015 |
}
|
| 5016 |
|
| 5017 |
/* The project's name (its display name, else its folder's), cut at its
|
| 5018 |
-
@@ -1781,8 +
|
| 5019 |
max-width: 28ch;
|
| 5020 |
overflow: hidden;
|
| 5021 |
color: var(--text-dim);
|
|
@@ -5026,7 +5055,7 @@ index d6b5535..9f01813 100644
|
|
| 5026 |
}
|
| 5027 |
|
| 5028 |
.project-picker-parent {
|
| 5029 |
-
@@ -1800,10 +
|
| 5030 |
text-overflow: ellipsis;
|
| 5031 |
}
|
| 5032 |
|
|
@@ -5037,7 +5066,7 @@ index d6b5535..9f01813 100644
|
|
| 5037 |
.project-picker-button:hover,
|
| 5038 |
.project-picker-button[aria-expanded="true"] {
|
| 5039 |
background: var(--bg-selected);
|
| 5040 |
-
@@ -1811,8 +
|
| 5041 |
}
|
| 5042 |
|
| 5043 |
.project-picker-button:focus-visible {
|
|
@@ -5049,7 +5078,7 @@ index d6b5535..9f01813 100644
|
|
| 5049 |
}
|
| 5050 |
|
| 5051 |
.project-picker-icon,
|
| 5052 |
-
@@ -1851,7 +
|
| 5053 |
path's color and code type, cut at its end. */
|
| 5054 |
.project-picker-label.is-alias {
|
| 5055 |
color: var(--text);
|
|
@@ -5059,7 +5088,7 @@ index d6b5535..9f01813 100644
|
|
| 5059 |
}
|
| 5060 |
|
| 5061 |
/* A path or a branch, cut at its left (rtl), the text itself kept left to
|
| 5062 |
-
@@ -1901,7 +
|
| 5063 |
border-color: color-mix(in srgb, var(--accent) 40%, transparent);
|
| 5064 |
background: color-mix(in srgb, var(--accent) 6%, transparent);
|
| 5065 |
color: var(--text-dim);
|
|
@@ -5069,7 +5098,7 @@ index d6b5535..9f01813 100644
|
|
| 5069 |
}
|
| 5070 |
|
| 5071 |
/* No worktree list here (the files tab): a box that says why, and does nothing. */
|
| 5072 |
-
@@ -1978,7 +
|
| 5073 |
box-sizing: border-box;
|
| 5074 |
flex: 0 0 24px;
|
| 5075 |
border: 1px solid var(--border);
|
|
@@ -5078,7 +5107,7 @@ index d6b5535..9f01813 100644
|
|
| 5078 |
background: transparent;
|
| 5079 |
color: var(--text-muted);
|
| 5080 |
cursor: pointer;
|
| 5081 |
-
@@ -2003,8 +
|
| 5082 |
|
| 5083 |
.file-viewer-mode-button:focus-visible,
|
| 5084 |
.file-viewer-icon-button:focus-visible {
|
|
@@ -5090,7 +5119,7 @@ index d6b5535..9f01813 100644
|
|
| 5090 |
}
|
| 5091 |
|
| 5092 |
.directory-picker-path:focus-visible {
|
| 5093 |
-
@@ -2037,7 +
|
| 5094 |
.markdown-file-preview pre {
|
| 5095 |
background: var(--bg-panel);
|
| 5096 |
border: 1px solid var(--border);
|
|
@@ -5099,7 +5128,7 @@ index d6b5535..9f01813 100644
|
|
| 5099 |
padding: 12px 16px;
|
| 5100 |
overflow-x: auto;
|
| 5101 |
font-family: var(--font-mono);
|
| 5102 |
-
@@ -2049,7 +
|
| 5103 |
font-size: 0.9em;
|
| 5104 |
background: var(--bg-panel);
|
| 5105 |
padding: 1px 5px;
|
|
@@ -5108,7 +5137,7 @@ index d6b5535..9f01813 100644
|
|
| 5108 |
}
|
| 5109 |
.markdown-file-preview pre code {
|
| 5110 |
background: none;
|
| 5111 |
-
@@ -2061,7 +
|
| 5112 |
margin-bottom: 20px;
|
| 5113 |
padding: 14px 16px 12px;
|
| 5114 |
border: 1px solid var(--border);
|
|
@@ -5117,7 +5146,7 @@ index d6b5535..9f01813 100644
|
|
| 5117 |
background: var(--bg-panel);
|
| 5118 |
}
|
| 5119 |
.markdown-frontmatter-title {
|
| 5120 |
-
@@ -2114,18 +
|
| 5121 |
text-underline-offset: 2px;
|
| 5122 |
}
|
| 5123 |
|
|
@@ -5136,7 +5165,7 @@ index d6b5535..9f01813 100644
|
|
| 5136 |
@keyframes drop-zone-in {
|
| 5137 |
from { opacity: 0; transform: scale(0.97); }
|
| 5138 |
to { opacity: 1; transform: scale(1); }
|
| 5139 |
-
@@ -2230,10 +
|
| 5140 |
.panel-resize-handle.is-resizing::after {
|
| 5141 |
background: color-mix(in srgb, var(--text-muted) 70%, var(--border));
|
| 5142 |
}
|
|
@@ -5147,7 +5176,7 @@ index d6b5535..9f01813 100644
|
|
| 5147 |
/* Desktop sidebar: animate width */
|
| 5148 |
@media (min-width: 641px) {
|
| 5149 |
.sidebar-overlay-backdrop {
|
| 5150 |
-
@@ -2264,119 +
|
| 5151 |
}
|
| 5152 |
}
|
| 5153 |
|
|
@@ -5267,7 +5296,7 @@ index d6b5535..9f01813 100644
|
|
| 5267 |
/* Mobile sidebar: slide in/out as overlay */
|
| 5268 |
@media (max-width: 640px) {
|
| 5269 |
.chat-stats-center {
|
| 5270 |
-
@@ -2401,31 +
|
| 5271 |
box-shadow: none;
|
| 5272 |
}
|
| 5273 |
|
|
@@ -5300,7 +5329,7 @@ index d6b5535..9f01813 100644
|
|
| 5300 |
display: none;
|
| 5301 |
}
|
| 5302 |
.directory-picker-back,
|
| 5303 |
-
@@ -2474,3 +
|
| 5304 |
display: none !important;
|
| 5305 |
}
|
| 5306 |
}
|
|
@@ -5468,7 +5497,10 @@ index d6b5535..9f01813 100644
|
|
| 5468 |
+ transition: opacity 0.12s;
|
| 5469 |
+}
|
| 5470 |
+:is(.turn-user, .turn-reply):is(:hover, :focus-within) .message-actions-reveal,
|
| 5471 |
-
+.message-actions-reveal.is-pinned
|
|
|
|
|
|
|
|
|
|
| 5472 |
+.message-meta {
|
| 5473 |
+ padding: 0 6px;
|
| 5474 |
+ color: var(--text-dim);
|
|
@@ -5476,10 +5508,18 @@ index d6b5535..9f01813 100644
|
|
| 5476 |
+ font-variant-numeric: tabular-nums;
|
| 5477 |
+ white-space: nowrap;
|
| 5478 |
+}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5479 |
+.turn-reply { margin: 0 0 22px; }
|
| 5480 |
+.turn-reply > .message-actions { margin: 4px 0 0 -6px; }
|
| 5481 |
+.turn-reply.is-step { margin: 0; }
|
|
|
|
|
|
|
|
|
|
| 5482 |
+.reply-blocks { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
|
|
| 5483 |
+
|
| 5484 |
+/* The steps of a turn: one summary line, "Ran 2 commands, read a file", that
|
| 5485 |
+ opens onto the steps themselves along a thin rail. */
|
|
@@ -5511,8 +5551,8 @@ index d6b5535..9f01813 100644
|
|
| 5511 |
+ position: relative;
|
| 5512 |
+ display: flex;
|
| 5513 |
+ flex-direction: column;
|
| 5514 |
-
+ gap:
|
| 5515 |
-
+ margin: 4px 0
|
| 5516 |
+ padding-left: 16px;
|
| 5517 |
+}
|
| 5518 |
+/* A drawing, not a border: the rail the steps hang from. */
|
|
@@ -5853,6 +5893,47 @@ index 62c25e4..a921c1d 100644
|
|
| 5853 |
<script
|
| 5854 |
dangerouslySetInnerHTML={{
|
| 5855 |
__html: THEME_INIT_SCRIPT,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5856 |
diff --git a/app/login/page.tsx b/app/login/page.tsx
|
| 5857 |
index 80264fe..6506201 100644
|
| 5858 |
--- a/app/login/page.tsx
|
|
@@ -5905,7 +5986,7 @@ index 79c8282..97731f1 100644
|
|
| 5905 |
lang: "en",
|
| 5906 |
icons: [
|
| 5907 |
diff --git a/app/settings.css b/app/settings.css
|
| 5908 |
-
index 0bf21eb..
|
| 5909 |
--- a/app/settings.css
|
| 5910 |
+++ b/app/settings.css
|
| 5911 |
@@ -35,14 +35,15 @@
|
|
@@ -5980,7 +6061,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 5980 |
.config-sidebar-group-label {
|
| 5981 |
display: flex;
|
| 5982 |
align-items: center;
|
| 5983 |
-
@@ -129,11 +133,
|
| 5984 |
padding: 4px 8px 3px;
|
| 5985 |
color: var(--text-dim);
|
| 5986 |
font-family: inherit;
|
|
@@ -5989,12 +6070,12 @@ index 0bf21eb..f4c84b5 100644
|
|
| 5989 |
+ font-size: 12px;
|
| 5990 |
+ font-weight: 400;
|
| 5991 |
line-height: 1.4;
|
| 5992 |
-
|
| 5993 |
- text-transform: uppercase;
|
| 5994 |
}
|
| 5995 |
|
| 5996 |
.config-sidebar-group-label-text {
|
| 5997 |
-
@@ -144,6 +
|
| 5998 |
white-space: nowrap;
|
| 5999 |
}
|
| 6000 |
|
|
@@ -6008,7 +6089,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6008 |
/* Sits in the heading row, so the sidebar gains no height for it. */
|
| 6009 |
.config-sidebar-group-switch {
|
| 6010 |
display: inline-flex;
|
| 6011 |
-
@@ -153,8 +
|
| 6012 |
}
|
| 6013 |
|
| 6014 |
.config-sidebar-group-count {
|
|
@@ -6019,7 +6100,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6019 |
}
|
| 6020 |
|
| 6021 |
/* Three lines, then scroll: the phone layout gives the whole sidebar 190px. */
|
| 6022 |
-
@@ -188,7 +
|
| 6023 |
height: 30px;
|
| 6024 |
padding: 0 8px;
|
| 6025 |
border: 0;
|
|
@@ -6028,7 +6109,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6028 |
outline: none;
|
| 6029 |
background: transparent;
|
| 6030 |
color: var(--text);
|
| 6031 |
-
@@ -202,17 +
|
| 6032 |
|
| 6033 |
.config-sidebar-item:not(:disabled):hover,
|
| 6034 |
.config-sidebar-item:not(:disabled):focus-visible {
|
|
@@ -6053,7 +6134,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6053 |
}
|
| 6054 |
|
| 6055 |
.config-sidebar-item:disabled {
|
| 6056 |
-
@@ -236,8 +
|
| 6057 |
.models-sidebar-badge {
|
| 6058 |
flex-shrink: 0;
|
| 6059 |
color: var(--text-dim);
|
|
@@ -6064,7 +6145,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6064 |
}
|
| 6065 |
|
| 6066 |
.oauth-paste-panel {
|
| 6067 |
-
@@ -277,9 +
|
| 6068 |
/* An input's intrinsic width would otherwise push the button out of a narrow pane. */
|
| 6069 |
min-width: 0;
|
| 6070 |
padding: 6px 9px;
|
|
@@ -6076,7 +6157,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6076 |
color: var(--text);
|
| 6077 |
font-family: var(--font-mono);
|
| 6078 |
font-size: 12px;
|
| 6079 |
-
@@ -290,9 +
|
| 6080 |
flex-shrink: 0;
|
| 6081 |
padding: 6px 12px;
|
| 6082 |
border: none;
|
|
@@ -6089,7 +6170,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6089 |
cursor: pointer;
|
| 6090 |
font-size: 12px;
|
| 6091 |
font-weight: 600;
|
| 6092 |
-
@@ -304,13 +
|
| 6093 |
cursor: not-allowed;
|
| 6094 |
}
|
| 6095 |
|
|
@@ -6106,7 +6187,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6106 |
}
|
| 6107 |
|
| 6108 |
.enabled-models-banner-text {
|
| 6109 |
-
@@ -370,8 +
|
| 6110 |
.enabled-models-count {
|
| 6111 |
margin-right: auto;
|
| 6112 |
color: var(--text-dim);
|
|
@@ -6117,7 +6198,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6117 |
}
|
| 6118 |
|
| 6119 |
.enabled-models-note {
|
| 6120 |
-
@@ -383,7 +
|
| 6121 |
.enabled-models-error {
|
| 6122 |
padding: 7px 9px;
|
| 6123 |
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
@@ -6126,7 +6207,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6126 |
color: #ef4444;
|
| 6127 |
font-size: 11px;
|
| 6128 |
line-height: 1.4;
|
| 6129 |
-
@@ -401,10 +
|
| 6130 |
box-sizing: border-box;
|
| 6131 |
height: 30px;
|
| 6132 |
padding: 0 9px;
|
|
@@ -6139,7 +6220,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6139 |
color: var(--text);
|
| 6140 |
font-family: inherit;
|
| 6141 |
font-size: 12px;
|
| 6142 |
-
@@ -413,8 +
|
| 6143 |
.enabled-models-list {
|
| 6144 |
max-height: 360px;
|
| 6145 |
border: 1px solid var(--border);
|
|
@@ -6150,7 +6231,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6150 |
overflow-y: auto;
|
| 6151 |
}
|
| 6152 |
|
| 6153 |
-
@@ -457,9 +
|
| 6154 |
.enabled-models-pin {
|
| 6155 |
flex-shrink: 0;
|
| 6156 |
padding: 1px 4px;
|
|
@@ -6163,7 +6244,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6163 |
font-size: 9px;
|
| 6164 |
}
|
| 6165 |
|
| 6166 |
-
@@ -496,10 +
|
| 6167 |
align-items: center;
|
| 6168 |
justify-content: space-between;
|
| 6169 |
gap: 18px;
|
|
@@ -6177,7 +6258,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6177 |
}
|
| 6178 |
|
| 6179 |
.agents-feature-copy {
|
| 6180 |
-
@@ -541,7 +
|
| 6181 |
height: 28px;
|
| 6182 |
padding: 0 6px;
|
| 6183 |
border: 1px solid var(--border);
|
|
@@ -6186,7 +6267,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6186 |
background: var(--bg);
|
| 6187 |
color: var(--text);
|
| 6188 |
font: inherit;
|
| 6189 |
-
@@ -566,7 +
|
| 6190 |
flex: 1;
|
| 6191 |
min-width: 0;
|
| 6192 |
min-height: 0;
|
|
@@ -6195,7 +6276,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6195 |
overflow-y: auto;
|
| 6196 |
font-family: inherit;
|
| 6197 |
font-size: 12px;
|
| 6198 |
-
@@ -628,13 +
|
| 6199 |
line-height: 1.35;
|
| 6200 |
}
|
| 6201 |
|
|
@@ -6207,12 +6288,12 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6207 |
+ font-size: 12px;
|
| 6208 |
+ font-weight: 400;
|
| 6209 |
line-height: 1.35;
|
| 6210 |
-
|
| 6211 |
- text-transform: uppercase;
|
| 6212 |
}
|
| 6213 |
|
| 6214 |
.config-field {
|
| 6215 |
-
@@ -662,7 +
|
| 6216 |
background: var(--border);
|
| 6217 |
border: 5px solid transparent;
|
| 6218 |
background-clip: padding-box;
|
|
@@ -6221,7 +6302,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6221 |
}
|
| 6222 |
|
| 6223 |
.config-field-label {
|
| 6224 |
-
@@ -697,15 +
|
| 6225 |
.config-scope-tag {
|
| 6226 |
flex-shrink: 0;
|
| 6227 |
padding: 1px 5px;
|
|
@@ -6240,7 +6321,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6240 |
}
|
| 6241 |
|
| 6242 |
.config-scope-switch-field {
|
| 6243 |
-
@@ -730,7 +
|
| 6244 |
height: 30px;
|
| 6245 |
overflow: hidden;
|
| 6246 |
border: 1px solid var(--border);
|
|
@@ -6249,7 +6330,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6249 |
}
|
| 6250 |
|
| 6251 |
.config-scope-switch-option {
|
| 6252 |
-
@@ -760,7 +
|
| 6253 |
.config-scope-switch.is-small {
|
| 6254 |
display: flex;
|
| 6255 |
height: auto;
|
|
@@ -6258,7 +6339,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6258 |
font-size: 12px;
|
| 6259 |
}
|
| 6260 |
|
| 6261 |
-
@@ -891,9 +
|
| 6262 |
width: 100%;
|
| 6263 |
height: 36px;
|
| 6264 |
padding: 0 11px;
|
|
@@ -6270,7 +6351,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6270 |
color: var(--text);
|
| 6271 |
font-family: var(--font-mono);
|
| 6272 |
font-size: 12px;
|
| 6273 |
-
@@ -935,8 +
|
| 6274 |
min-height: 30px;
|
| 6275 |
padding: 6px 9px;
|
| 6276 |
border: 1px solid var(--border);
|
|
@@ -6281,7 +6362,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6281 |
color: var(--text-dim);
|
| 6282 |
cursor: pointer;
|
| 6283 |
font-family: var(--font-mono);
|
| 6284 |
-
@@ -975,12 +
|
| 6285 |
white-space: pre-wrap;
|
| 6286 |
}
|
| 6287 |
|
|
@@ -6300,7 +6381,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6300 |
}
|
| 6301 |
|
| 6302 |
.config-notice.has-action {
|
| 6303 |
-
@@ -1104,8 +
|
| 6304 |
align-items: center;
|
| 6305 |
gap: 8px;
|
| 6306 |
min-height: 52px;
|
|
@@ -6316,7 +6397,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6316 |
}
|
| 6317 |
|
| 6318 |
.config-footer-status {
|
| 6319 |
-
@@ -1159,
|
| 6320 |
gap: 6px;
|
| 6321 |
border-width: 1px;
|
| 6322 |
border-style: solid;
|
|
@@ -6324,8 +6405,9 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6324 |
+ border-radius: var(--radius-sm);
|
| 6325 |
cursor: pointer;
|
| 6326 |
font-family: inherit;
|
| 6327 |
-
|
| 6328 |
-
|
|
|
|
| 6329 |
transition: background-color 0.14s ease, border-color 0.14s ease, color 0.14s ease, box-shadow 0.14s ease;
|
| 6330 |
}
|
| 6331 |
|
|
@@ -6381,7 +6463,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6381 |
}
|
| 6382 |
|
| 6383 |
.config-button-ghost {
|
| 6384 |
-
@@ -1206,24 +
|
| 6385 |
}
|
| 6386 |
|
| 6387 |
.config-button-primary:not(:disabled):hover {
|
|
@@ -6421,7 +6503,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6421 |
outline-offset: 2px;
|
| 6422 |
}
|
| 6423 |
|
| 6424 |
-
@@ -1248,33 +
|
| 6425 |
margin-left: auto;
|
| 6426 |
}
|
| 6427 |
|
|
@@ -6475,7 +6557,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6475 |
.config-switch::before {
|
| 6476 |
content: "";
|
| 6477 |
position: absolute;
|
| 6478 |
-
@@ -1283,7 +
|
| 6479 |
|
| 6480 |
.config-switch:disabled {
|
| 6481 |
cursor: default;
|
|
@@ -6484,7 +6566,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6484 |
}
|
| 6485 |
|
| 6486 |
.config-switch.is-loading:disabled {
|
| 6487 |
-
@@ -1294,42 +
|
| 6488 |
position: absolute;
|
| 6489 |
top: 2px;
|
| 6490 |
left: 2px;
|
|
@@ -6541,7 +6623,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6541 |
}
|
| 6542 |
|
| 6543 |
.config-list-action-button {
|
| 6544 |
-
@@ -1342,7 +
|
| 6545 |
min-height: 30px;
|
| 6546 |
padding: 0 8px;
|
| 6547 |
border: 0;
|
|
@@ -6550,7 +6632,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6550 |
background: transparent;
|
| 6551 |
color: var(--text-dim);
|
| 6552 |
cursor: pointer;
|
| 6553 |
-
@@ -1358,7 +
|
| 6554 |
|
| 6555 |
.config-list-action-button[aria-current="page"] {
|
| 6556 |
background: var(--bg-selected);
|
|
@@ -6559,7 +6641,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6559 |
}
|
| 6560 |
|
| 6561 |
.config-list-action-button:disabled {
|
| 6562 |
-
@@ -1382,6 +
|
| 6563 |
opacity: 0.7;
|
| 6564 |
}
|
| 6565 |
|
|
@@ -6574,7 +6656,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6574 |
.settings-dialog-backdrop {
|
| 6575 |
position: fixed;
|
| 6576 |
inset: 0;
|
| 6577 |
-
@@ -1389,7 +
|
| 6578 |
display: flex;
|
| 6579 |
align-items: center;
|
| 6580 |
justify-content: center;
|
|
@@ -6583,7 +6665,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6583 |
}
|
| 6584 |
|
| 6585 |
/* Focus moves to the dialog itself when Settings opens (tabIndex -1); it is not a control, so no ring. */
|
| 6586 |
-
@@ -1398,127 +
|
| 6587 |
}
|
| 6588 |
|
| 6589 |
.settings-dialog-surface {
|
|
@@ -6839,7 +6921,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 6839 |
}
|
| 6840 |
|
| 6841 |
.settings-dialog-main {
|
| 6842 |
-
@@ -1537,136 +
|
| 6843 |
min-height: 0;
|
| 6844 |
}
|
| 6845 |
|
|
@@ -7116,7 +7198,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7116 |
font-family: var(--font-mono);
|
| 7117 |
font-weight: var(--font-mono-weight);
|
| 7118 |
}
|
| 7119 |
-
@@ -1676,197 +
|
| 7120 |
}
|
| 7121 |
|
| 7122 |
.settings-font-input:focus-visible {
|
|
@@ -7572,7 +7654,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7572 |
}
|
| 7573 |
|
| 7574 |
@media (max-width: 640px) {
|
| 7575 |
-
@@ -1887,13 +
|
| 7576 |
width: 100%;
|
| 7577 |
height: 190px;
|
| 7578 |
border-right: 0;
|
|
@@ -7588,7 +7670,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7588 |
}
|
| 7589 |
|
| 7590 |
.agents-feature-actions {
|
| 7591 |
-
@@ -1905,29 +
|
| 7592 |
padding: 14px;
|
| 7593 |
}
|
| 7594 |
|
|
@@ -7618,7 +7700,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7618 |
}
|
| 7619 |
|
| 7620 |
@keyframes saved-pop {
|
| 7621 |
-
@@ -2066,7 +
|
| 7622 |
max-width: 100%;
|
| 7623 |
padding: 0 5px;
|
| 7624 |
border: 1px solid var(--border);
|
|
@@ -7627,7 +7709,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7627 |
background: var(--bg-panel);
|
| 7628 |
color: var(--text);
|
| 7629 |
font-family: var(--font-mono);
|
| 7630 |
-
@@ -2085,7 +
|
| 7631 |
padding: 6px 8px;
|
| 7632 |
overflow: auto;
|
| 7633 |
border: 1px solid var(--border);
|
|
@@ -7636,7 +7718,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7636 |
background: var(--bg-panel);
|
| 7637 |
color: var(--text-muted);
|
| 7638 |
font-family: var(--font-mono);
|
| 7639 |
-
@@ -2172,9 +
|
| 7640 |
min-width: 0;
|
| 7641 |
height: 32px;
|
| 7642 |
padding: 0 10px;
|
|
@@ -7648,7 +7730,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7648 |
color: var(--text);
|
| 7649 |
font-family: var(--font-mono);
|
| 7650 |
font-size: 12px;
|
| 7651 |
-
@@ -2266,10 +
|
| 7652 |
max-width: 100%;
|
| 7653 |
max-height: 100%;
|
| 7654 |
overflow: hidden;
|
|
@@ -7662,7 +7744,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7662 |
}
|
| 7663 |
|
| 7664 |
/* Focus moves to the dialog itself when it opens (tabIndex -1); it is not a control, so no ring. */
|
| 7665 |
-
@@ -2282,7 +
|
| 7666 |
flex: 1 1 auto;
|
| 7667 |
gap: 12px;
|
| 7668 |
min-height: 0;
|
|
@@ -7671,7 +7753,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7671 |
overflow: auto;
|
| 7672 |
overscroll-behavior: contain;
|
| 7673 |
}
|
| 7674 |
-
@@ -2315,7 +
|
| 7675 |
margin-top: 10px;
|
| 7676 |
padding: 8px 10px;
|
| 7677 |
border: 1px solid var(--border);
|
|
@@ -7680,7 +7762,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7680 |
background: var(--bg);
|
| 7681 |
color: var(--text);
|
| 7682 |
font-family: var(--font-mono);
|
| 7683 |
-
@@ -2333,7 +
|
| 7684 |
.project-trust-error {
|
| 7685 |
flex-shrink: 0;
|
| 7686 |
max-height: 30vh;
|
|
@@ -7689,7 +7771,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7689 |
overflow: auto;
|
| 7690 |
color: #ef4444;
|
| 7691 |
font-size: 12px;
|
| 7692 |
-
@@ -2353,19 +
|
| 7693 |
flex-shrink: 0;
|
| 7694 |
justify-content: flex-end;
|
| 7695 |
gap: 8px;
|
|
@@ -7717,7 +7799,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7717 |
}
|
| 7718 |
|
| 7719 |
.project-trust-button:disabled {
|
| 7720 |
-
@@ -2373,10 +
|
| 7721 |
}
|
| 7722 |
|
| 7723 |
.project-trust-button.is-primary {
|
|
@@ -7730,7 +7812,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7730 |
}
|
| 7731 |
|
| 7732 |
.project-trust-button.is-primary:disabled {
|
| 7733 |
-
@@ -2450,7 +
|
| 7734 |
min-width: 0;
|
| 7735 |
padding: 8px 10px;
|
| 7736 |
border: 1px solid var(--border);
|
|
@@ -7739,7 +7821,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7739 |
background: var(--bg);
|
| 7740 |
}
|
| 7741 |
|
| 7742 |
-
@@ -2472,7 +
|
| 7743 |
.project-trust-mcp-tag {
|
| 7744 |
flex-shrink: 0;
|
| 7745 |
padding: 1px 5px;
|
|
@@ -7748,7 +7830,7 @@ index 0bf21eb..f4c84b5 100644
|
|
| 7748 |
background: rgba(120, 120, 120, 0.12);
|
| 7749 |
color: var(--text-dim);
|
| 7750 |
font-size: 10px;
|
| 7751 |
-
@@ -2493,7 +
|
| 7752 |
display: inline-block;
|
| 7753 |
margin: 2px 4px 0 0;
|
| 7754 |
padding: 0 4px;
|
|
@@ -9235,7 +9317,7 @@ index 737e7a5..c098317 100644
|
|
| 9235 |
|
| 9236 |
.sidebar-sheet .sidebar-menu-question-text {
|
| 9237 |
diff --git a/app/sidebar.css b/app/sidebar.css
|
| 9238 |
-
index 86728a2..
|
| 9239 |
--- a/app/sidebar.css
|
| 9240 |
+++ b/app/sidebar.css
|
| 9241 |
@@ -91,8 +91,9 @@
|
|
@@ -9347,7 +9429,7 @@ index 86728a2..d1346be 100644
|
|
| 9347 |
border-radius: 50%;
|
| 9348 |
opacity: 0.35;
|
| 9349 |
animation: session-tree-status-pulse 1.6s ease-in-out infinite;
|
| 9350 |
-
@@ -297,20 +278,
|
| 9351 |
}
|
| 9352 |
}
|
| 9353 |
|
|
@@ -9364,7 +9446,6 @@ index 86728a2..d1346be 100644
|
|
| 9364 |
- font-size: 12px;
|
| 9365 |
- text-overflow: ellipsis;
|
| 9366 |
+ font-size: 13px;
|
| 9367 |
-
+ letter-spacing: -0.005em;
|
| 9368 |
white-space: nowrap;
|
| 9369 |
+ -webkit-mask-image: linear-gradient(to right, black calc(100% - 20px), transparent);
|
| 9370 |
+ mask-image: linear-gradient(to right, black calc(100% - 20px), transparent);
|
|
@@ -9376,7 +9457,7 @@ index 86728a2..d1346be 100644
|
|
| 9376 |
}
|
| 9377 |
|
| 9378 |
.session-tree-session.is-archived .session-tree-title {
|
| 9379 |
-
@@ -321,6 +
|
| 9380 |
from its source ("… · 3f9a"), never the suffix. */
|
| 9381 |
.session-tree-title.has-fork-suffix {
|
| 9382 |
display: flex;
|
|
@@ -9385,7 +9466,7 @@ index 86728a2..d1346be 100644
|
|
| 9385 |
}
|
| 9386 |
|
| 9387 |
.session-tree-title-base {
|
| 9388 |
-
@@ -384,16 +
|
| 9389 |
align-items: center;
|
| 9390 |
}
|
| 9391 |
|
|
@@ -9413,7 +9494,7 @@ index 86728a2..d1346be 100644
|
|
| 9413 |
color: var(--text-dim);
|
| 9414 |
}
|
| 9415 |
|
| 9416 |
-
@@ -408,11 +
|
| 9417 |
background: var(--bg-hover);
|
| 9418 |
}
|
| 9419 |
|
|
@@ -9435,7 +9516,7 @@ index 86728a2..d1346be 100644
|
|
| 9435 |
}
|
| 9436 |
|
| 9437 |
.session-tree-meta.is-running {
|
| 9438 |
-
@@ -436,7 +
|
| 9439 |
* :focus-within fallback in the @supports block after the group rules.
|
| 9440 |
*/
|
| 9441 |
.session-tree-session:has(:focus-visible) .session-tree-action {
|
|
@@ -9447,7 +9528,7 @@ index 86728a2..d1346be 100644
|
|
| 9447 |
}
|
| 9448 |
|
| 9449 |
.session-tree-session:has(:focus-visible) .session-tree-meta {
|
| 9450 |
-
@@ -454,7 +
|
| 9451 |
padding: 0 8px;
|
| 9452 |
box-sizing: border-box;
|
| 9453 |
border: 1px solid var(--accent);
|
|
@@ -9456,7 +9537,7 @@ index 86728a2..d1346be 100644
|
|
| 9456 |
outline: none;
|
| 9457 |
background: var(--bg);
|
| 9458 |
color: var(--text);
|
| 9459 |
-
@@ -483,7 +
|
| 9460 |
gap: 4px;
|
| 9461 |
height: 24px;
|
| 9462 |
padding: 0 10px;
|
|
@@ -9465,7 +9546,7 @@ index 86728a2..d1346be 100644
|
|
| 9465 |
font-size: 12px;
|
| 9466 |
white-space: nowrap;
|
| 9467 |
}
|
| 9468 |
-
@@ -518,8 +
|
| 9469 |
right: var(--session-tree-inset-right);
|
| 9470 |
left: var(--session-tree-inset-left);
|
| 9471 |
gap: 2px;
|
|
@@ -9476,7 +9557,7 @@ index 86728a2..d1346be 100644
|
|
| 9476 |
-webkit-user-select: none;
|
| 9477 |
user-select: none;
|
| 9478 |
-webkit-touch-callout: none;
|
| 9479 |
-
@@ -548,7 +
|
| 9480 |
position: absolute;
|
| 9481 |
right: var(--session-tree-inset-right);
|
| 9482 |
left: var(--session-tree-inset-left);
|
|
@@ -9485,7 +9566,7 @@ index 86728a2..d1346be 100644
|
|
| 9485 |
background: color-mix(in srgb, var(--bg-panel) 55%, transparent);
|
| 9486 |
pointer-events: none;
|
| 9487 |
}
|
| 9488 |
-
@@ -589,7 +
|
| 9489 |
height: 22px;
|
| 9490 |
padding: 0 10px;
|
| 9491 |
border: 1px solid var(--border);
|
|
@@ -9494,7 +9575,7 @@ index 86728a2..d1346be 100644
|
|
| 9494 |
background: var(--bg-panel);
|
| 9495 |
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
|
| 9496 |
color: var(--text);
|
| 9497 |
-
@@ -618,7 +
|
| 9498 |
padding: 0 0 0 5px;
|
| 9499 |
color: var(--text-muted);
|
| 9500 |
font-size: 12px;
|
|
@@ -9503,7 +9584,7 @@ index 86728a2..d1346be 100644
|
|
| 9503 |
text-align: left;
|
| 9504 |
white-space: nowrap;
|
| 9505 |
}
|
| 9506 |
-
@@ -669,19 +
|
| 9507 |
width: 6px;
|
| 9508 |
height: 6px;
|
| 9509 |
border-radius: 50%;
|
|
@@ -9534,7 +9615,7 @@ index 86728a2..d1346be 100644
|
|
| 9534 |
}
|
| 9535 |
|
| 9536 |
.session-tree-group:hover .session-tree-summary,
|
| 9537 |
-
@@ -690,7 +
|
| 9538 |
}
|
| 9539 |
|
| 9540 |
.session-tree-group:has(:focus-visible) .session-tree-group-actions {
|
|
@@ -9546,7 +9627,7 @@ index 86728a2..d1346be 100644
|
|
| 9546 |
}
|
| 9547 |
|
| 9548 |
.session-tree-group:has(:focus-visible) .session-tree-summary {
|
| 9549 |
-
@@ -698,9 +
|
| 9550 |
}
|
| 9551 |
|
| 9552 |
@supports not selector(:has(*)) {
|
|
@@ -9567,7 +9648,7 @@ index 86728a2..d1346be 100644
|
|
| 9567 |
}
|
| 9568 |
|
| 9569 |
.session-tree-session:focus-within .session-tree-meta,
|
| 9570 |
-
@@ -726,7 +
|
| 9571 |
width: 22px;
|
| 9572 |
height: 22px;
|
| 9573 |
padding: 0;
|
|
@@ -9576,7 +9657,7 @@ index 86728a2..d1346be 100644
|
|
| 9576 |
color: var(--text-dim);
|
| 9577 |
}
|
| 9578 |
|
| 9579 |
-
@@ -787,7 +
|
| 9580 |
gap: 3px;
|
| 9581 |
min-width: 0;
|
| 9582 |
padding: 0 4px 0 5px;
|
|
@@ -9585,7 +9666,7 @@ index 86728a2..d1346be 100644
|
|
| 9585 |
color: var(--text-dim);
|
| 9586 |
font-size: 12px;
|
| 9587 |
text-align: left;
|
| 9588 |
-
@@ -838,16 +
|
| 9589 |
|
| 9590 |
/* Phone layout: 44px rows, a step larger type for titles and project names,
|
| 9591 |
⋯ and the group buttons always shown, no archive quick button. */
|
|
@@ -9609,7 +9690,7 @@ index 86728a2..d1346be 100644
|
|
| 9609 |
}
|
| 9610 |
|
| 9611 |
.session-tree.is-mobile .session-tree-session:hover .session-tree-meta {
|
| 9612 |
-
@@ -859,7 +
|
| 9613 |
}
|
| 9614 |
|
| 9615 |
.session-tree.is-mobile .session-tree-group-actions {
|
|
@@ -9621,7 +9702,7 @@ index 86728a2..d1346be 100644
|
|
| 9621 |
}
|
| 9622 |
|
| 9623 |
.session-tree.is-mobile .session-tree-group .session-tree-summary {
|
| 9624 |
-
@@ -884,7 +
|
| 9625 |
/* Touch screens wider than a phone have no hover: keep ⋯ reachable. */
|
| 9626 |
@media (hover: none) {
|
| 9627 |
.session-tree-more-action {
|
|
@@ -9633,7 +9714,7 @@ index 86728a2..d1346be 100644
|
|
| 9633 |
}
|
| 9634 |
}
|
| 9635 |
|
| 9636 |
-
@@ -942,9 +
|
| 9637 |
display: flex;
|
| 9638 |
flex: none;
|
| 9639 |
align-items: stretch;
|
|
@@ -9645,7 +9726,7 @@ index 86728a2..d1346be 100644
|
|
| 9645 |
}
|
| 9646 |
|
| 9647 |
/* The tablist itself holds only the two tabs (ARIA allows nothing else in it). */
|
| 9648 |
-
@@ -954,44 +
|
| 9649 |
align-items: stretch;
|
| 9650 |
}
|
| 9651 |
|
|
@@ -9697,7 +9778,7 @@ index 86728a2..d1346be 100644
|
|
| 9697 |
.sidebar-tab-icon {
|
| 9698 |
flex: none;
|
| 9699 |
color: var(--text-dim);
|
| 9700 |
-
@@ -1006,17 +
|
| 9701 |
|
| 9702 |
.sidebar-tab.is-selected,
|
| 9703 |
.sidebar-search-toggle.is-active {
|
|
@@ -9717,7 +9798,7 @@ index 86728a2..d1346be 100644
|
|
| 9717 |
}
|
| 9718 |
|
| 9719 |
.sidebar-tab-count {
|
| 9720 |
-
@@ -1036,27 +
|
| 9721 |
beside the two cells at the sidebar's 180px minimum, and at 11px so do
|
| 9722 |
the versions with a digit to spare). */
|
| 9723 |
.sidebar-brand {
|
|
@@ -9740,13 +9821,13 @@ index 86728a2..d1346be 100644
|
|
| 9740 |
- font-family: var(--font-mono);
|
| 9741 |
- font-size: 15px;
|
| 9742 |
- font-weight: 700;
|
|
|
|
|
|
|
| 9743 |
+ color: var(--text-strong);
|
| 9744 |
+ font-family: var(--font-ui);
|
| 9745 |
+ font-size: 13px;
|
| 9746 |
+ font-weight: 600;
|
| 9747 |
+ font-variant-numeric: tabular-nums;
|
| 9748 |
-
letter-spacing: -0.01em;
|
| 9749 |
-
- line-height: normal;
|
| 9750 |
+ line-height: 1;
|
| 9751 |
text-align: left;
|
| 9752 |
white-space: nowrap;
|
|
@@ -9765,7 +9846,7 @@ index 86728a2..d1346be 100644
|
|
| 9765 |
}
|
| 9766 |
|
| 9767 |
.sidebar-brand:focus {
|
| 9768 |
-
@@ -1064,8 +
|
| 9769 |
}
|
| 9770 |
|
| 9771 |
.sidebar-brand:focus-visible {
|
|
@@ -9778,7 +9859,7 @@ index 86728a2..d1346be 100644
|
|
| 9778 |
}
|
| 9779 |
|
| 9780 |
.sidebar-header-spacer {
|
| 9781 |
-
@@ -1078,7 +
|
| 9782 |
}
|
| 9783 |
|
| 9784 |
.sidebar-search-toggle {
|
|
@@ -9787,7 +9868,7 @@ index 86728a2..d1346be 100644
|
|
| 9787 |
padding: 0;
|
| 9788 |
}
|
| 9789 |
|
| 9790 |
-
@@ -1093,7 +
|
| 9791 |
label is still each one's name. */
|
| 9792 |
.sidebar-header[data-fit="1"] .sidebar-new-button,
|
| 9793 |
.sidebar-header[data-fit="2"] .sidebar-new-button {
|
|
@@ -9796,7 +9877,7 @@ index 86728a2..d1346be 100644
|
|
| 9796 |
padding: 0;
|
| 9797 |
}
|
| 9798 |
|
| 9799 |
-
@@ -1105,6 +
|
| 9800 |
font-size: 11px;
|
| 9801 |
}
|
| 9802 |
|
|
@@ -9808,7 +9889,7 @@ index 86728a2..d1346be 100644
|
|
| 9808 |
.sidebar-header[data-fit="1"] .sidebar-new-label,
|
| 9809 |
.sidebar-header[data-fit="2"] .sidebar-new-label,
|
| 9810 |
.sidebar-header[data-fit="2"] .sidebar-tab-label {
|
| 9811 |
-
@@ -1122,8 +
|
| 9812 |
.sidebar-archive-back:focus-visible,
|
| 9813 |
.sidebar-tool-button:focus-visible,
|
| 9814 |
.sidebar-files-section-toggle:focus-visible {
|
|
@@ -9820,7 +9901,7 @@ index 86728a2..d1346be 100644
|
|
| 9820 |
}
|
| 9821 |
|
| 9822 |
.sidebar-panel {
|
| 9823 |
-
@@ -1202,7 +
|
| 9824 |
the root too), and its time 4px inside the row. The header row's keys and
|
| 9825 |
the picker's boxes end there. */
|
| 9826 |
.session-sidebar.is-files-below {
|
|
@@ -9829,7 +9910,7 @@ index 86728a2..d1346be 100644
|
|
| 9829 |
}
|
| 9830 |
|
| 9831 |
/* The files section's header row (files below the sessions only), one row
|
| 9832 |
-
@@ -1246,7 +
|
| 9833 |
padding: 0 5px;
|
| 9834 |
overflow: hidden;
|
| 9835 |
border: 0;
|
|
@@ -9838,7 +9919,7 @@ index 86728a2..d1346be 100644
|
|
| 9838 |
background: transparent;
|
| 9839 |
color: var(--text-muted);
|
| 9840 |
font: inherit;
|
| 9841 |
-
@@ -1272,10 +
|
| 9842 |
.sidebar-files-section-label {
|
| 9843 |
min-width: 0;
|
| 9844 |
overflow: hidden;
|
|
@@ -9850,7 +9931,7 @@ index 86728a2..d1346be 100644
|
|
| 9850 |
}
|
| 9851 |
|
| 9852 |
/* The changed files' count, as the pinned section's (.session-tree-pinned-count). */
|
| 9853 |
-
@@ -1294,7 +
|
| 9854 |
display: flex;
|
| 9855 |
flex: 1000 0 calc(7 * 18px);
|
| 9856 |
align-items: center;
|
|
@@ -9859,7 +9940,7 @@ index 86728a2..d1346be 100644
|
|
| 9859 |
}
|
| 9860 |
|
| 9861 |
/* The section's keys: the files tab's square keys, a step under the row's
|
| 9862 |
-
@@ -1302,9 +
|
| 9863 |
.sidebar-files-keys .sidebar-tool-button {
|
| 9864 |
flex: 1 1 0;
|
| 9865 |
min-width: 18px;
|
|
@@ -9872,7 +9953,7 @@ index 86728a2..d1346be 100644
|
|
| 9872 |
}
|
| 9873 |
|
| 9874 |
/* Under that row (or the whole files tab): the head, then the tree filling
|
| 9875 |
-
@@ -1317,31 +
|
| 9876 |
min-height: 0;
|
| 9877 |
}
|
| 9878 |
|
|
@@ -9905,7 +9986,7 @@ index 86728a2..d1346be 100644
|
|
| 9905 |
slides in over the tree, which stays mounted under it (hidden once the
|
| 9906 |
archive is in), and away again. SessionTree fills each. */
|
| 9907 |
.sidebar-sessions-views {
|
| 9908 |
-
@@ -1413,7 +
|
| 9909 |
height: 26px;
|
| 9910 |
padding: 0 6px;
|
| 9911 |
border: 0;
|
|
@@ -9914,7 +9995,7 @@ index 86728a2..d1346be 100644
|
|
| 9914 |
background: transparent;
|
| 9915 |
color: var(--text-muted);
|
| 9916 |
font-size: 12px;
|
| 9917 |
-
@@ -1479,10 +
|
| 9918 |
six (the changes view is disabled, not gone, without changes), so nothing
|
| 9919 |
moves as the count flips. At the sidebar's 180px minimum they give way
|
| 9920 |
together, down to 21px wide, and the gap before the views closes. */
|
|
@@ -9927,7 +10008,7 @@ index 86728a2..d1346be 100644
|
|
| 9927 |
margin-top: 4px;
|
| 9928 |
}
|
| 9929 |
|
| 9930 |
-
@@ -1492,17 +
|
| 9931 |
margin-left: auto;
|
| 9932 |
}
|
| 9933 |
|
|
@@ -9951,7 +10032,7 @@ index 86728a2..d1346be 100644
|
|
| 9951 |
background: transparent;
|
| 9952 |
color: var(--text-dim);
|
| 9953 |
cursor: pointer;
|
| 9954 |
-
@@ -1510,8 +
|
| 9955 |
}
|
| 9956 |
|
| 9957 |
.sidebar-tool-button:not(:disabled):hover {
|
|
@@ -9964,10 +10045,10 @@ index 86728a2..d1346be 100644
|
|
| 9964 |
.sidebar-tool-button.is-active,
|
| 9965 |
diff --git a/app/ui.css b/app/ui.css
|
| 9966 |
new file mode 100644
|
| 9967 |
-
index 0000000..
|
| 9968 |
--- /dev/null
|
| 9969 |
+++ b/app/ui.css
|
| 9970 |
-
@@ -0,0 +1,
|
| 9971 |
+/* The shared components on Base UI (components/ui/*): menus, tooltips,
|
| 9972 |
+ popovers and the composer's suggestion list. Popups are portaled to <body>,
|
| 9973 |
+ so they stack by the positioner's z-index, above every pane. */
|
|
@@ -10238,7 +10319,6 @@ index 0000000..3426491
|
|
| 10238 |
+ background: color-mix(in srgb, var(--primary-contrast) 16%, transparent);
|
| 10239 |
+ font-family: inherit;
|
| 10240 |
+ font-size: 11px;
|
| 10241 |
-
+ letter-spacing: 0.04em;
|
| 10242 |
+}
|
| 10243 |
+
|
| 10244 |
+/* ---------------------------------------------------------------- popovers */
|
|
@@ -20817,6 +20897,38 @@ index 25e4a63..171bf7c 100644
|
|
| 20817 |
</div>
|
| 20818 |
);
|
| 20819 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20820 |
diff --git a/components/MessageView.test.mjs b/components/MessageView.test.mjs
|
| 20821 |
index 515b033..12d546b 100644
|
| 20822 |
--- a/components/MessageView.test.mjs
|
|
@@ -20856,7 +20968,7 @@ index 515b033..12d546b 100644
|
|
| 20856 |
|
| 20857 |
test("collapses a displayed custom message from its header alone", () => {
|
| 20858 |
diff --git a/components/MessageView.tsx b/components/MessageView.tsx
|
| 20859 |
-
index f2ff1de..
|
| 20860 |
--- a/components/MessageView.tsx
|
| 20861 |
+++ b/components/MessageView.tsx
|
| 20862 |
@@ -5,13 +5,16 @@ import ReactMarkdown from "react-markdown";
|
|
@@ -21318,7 +21430,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 21318 |
}: {
|
| 21319 |
message: AssistantMessage;
|
| 21320 |
isStreaming?: boolean;
|
| 21321 |
-
@@ -669,
|
| 21322 |
onCompact?: () => void;
|
| 21323 |
isCompacting?: boolean;
|
| 21324 |
compactError?: string | null;
|
|
@@ -21327,7 +21439,14 @@ index f2ff1de..b18cfc6 100644
|
|
| 21327 |
}) {
|
| 21328 |
const { t } = useI18n();
|
| 21329 |
const time = showTimestamp ? formatTime(message.timestamp) : null;
|
| 21330 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21331 |
const providerError = getAssistantErrorMessage(message, { isStreaming });
|
| 21332 |
const truncated = isAssistantTruncated(message, { isStreaming });
|
| 21333 |
const unansweredTruncation = truncated && !hasAssistantAnswer(message);
|
|
@@ -21335,7 +21454,22 @@ index f2ff1de..b18cfc6 100644
|
|
| 21335 |
const [copied, setCopied] = useState(false);
|
| 21336 |
const streamStartRef = useRef<StreamRateStart | null>(null);
|
| 21337 |
const rateKey = streamRateKey(message);
|
| 21338 |
-
@@ -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21339 |
|
| 21340 |
if (blocks.length === 0 && !isStreaming && !providerError && !truncated) return null;
|
| 21341 |
|
|
@@ -21354,7 +21488,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 21354 |
- style={{ marginBottom: 16 }}
|
| 21355 |
- onMouseEnter={() => setHovered(true)}
|
| 21356 |
- onMouseLeave={() => setHovered(false)}
|
| 21357 |
-
+ className={`turn-reply${
|
| 21358 |
>
|
| 21359 |
- {/* Model label */}
|
| 21360 |
- <div
|
|
@@ -21484,7 +21618,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 21484 |
)}
|
| 21485 |
</div>
|
| 21486 |
)}
|
| 21487 |
-
@@ -928,51 +
|
| 21488 |
<TurnWrittenFiles files={writtenFiles} onOpenFile={onOpenFile} />
|
| 21489 |
)}
|
| 21490 |
|
|
@@ -21497,14 +21631,8 @@ index f2ff1de..b18cfc6 100644
|
|
| 21497 |
- </div>
|
| 21498 |
- )}
|
| 21499 |
- {textContent && !isStreaming && (
|
| 21500 |
-
|
| 21501 |
-
|
| 21502 |
-
+ <div className="message-actions">
|
| 21503 |
-
<button
|
| 21504 |
-
+ type="button"
|
| 21505 |
-
+ className="ui-btn ui-btn--xs ui-btn--icon"
|
| 21506 |
-
+ data-done={copied || undefined}
|
| 21507 |
-
onClick={copyContent}
|
| 21508 |
- title={t("i18n.copyMessage")}
|
| 21509 |
- style={{
|
| 21510 |
- display: "flex", alignItems: "center", gap: 4,
|
|
@@ -21521,9 +21649,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 21521 |
- }}
|
| 21522 |
- onMouseEnter={(e) => { if (!copied) e.currentTarget.style.color = "var(--accent)"; }}
|
| 21523 |
- onMouseLeave={(e) => { if (!copied) e.currentTarget.style.color = "var(--text-dim)"; }}
|
| 21524 |
-
|
| 21525 |
-
+ aria-label={copied ? t("i18n.copied") : t("i18n.copyMessage")}
|
| 21526 |
-
>
|
| 21527 |
- {copied ? (
|
| 21528 |
- <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
| 21529 |
- <polyline points="20 6 9 17 4 12" />
|
|
@@ -21533,20 +21659,32 @@ index f2ff1de..b18cfc6 100644
|
|
| 21533 |
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
| 21534 |
- <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
| 21535 |
- </svg>
|
| 21536 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21537 |
- {copied ? t("i18n.copied") : t("i18n.copy")}
|
| 21538 |
-
|
| 21539 |
-
</button>
|
| 21540 |
- )}
|
| 21541 |
- {time && !isStreaming && (
|
| 21542 |
- <span style={{ fontSize: 10, color: "var(--text-dim)", marginLeft: "auto" }}>{time}</span>
|
| 21543 |
- )}
|
| 21544 |
- </div>
|
| 21545 |
-
+ <div className="message-actions-reveal">
|
| 21546 |
-
+ {modelName && <span className="message-meta">{modelName}</span>}
|
| 21547 |
-
+ {shownUsage && formatUsageShort(shownUsage) && (
|
| 21548 |
-
+ <span className="message-meta" title={formatUsage(shownUsage)}>{formatUsageShort(shownUsage)}</span>
|
| 21549 |
-
+ )}
|
| 21550 |
+ {time && <span className="message-meta">{time}</span>}
|
| 21551 |
+ </div>
|
| 21552 |
+ </div>
|
|
@@ -21554,7 +21692,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 21554 |
</div>
|
| 21555 |
);
|
| 21556 |
}
|
| 21557 |
-
@@ -988,7 +
|
| 21558 |
const tc = block as ToolCallContent;
|
| 21559 |
const result = toolResults?.get(tc.toolCallId);
|
| 21560 |
const duration = toolCallDurations?.get(tc.toolCallId);
|
|
@@ -21563,7 +21701,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 21563 |
}
|
| 21564 |
return null;
|
| 21565 |
}
|
| 21566 |
-
@@ -1052,62 +
|
| 21567 |
}, [expanded, block.deferred, content, sessionId, entryId, blockIndex]);
|
| 21568 |
|
| 21569 |
return (
|
|
@@ -21650,7 +21788,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 21650 |
</div>
|
| 21651 |
);
|
| 21652 |
}
|
| 21653 |
-
@@ -1137,7 +
|
| 21654 |
return `${Math.floor(minutes / 60)}h ${minutes % 60}m ${remainder}s`;
|
| 21655 |
}
|
| 21656 |
|
|
@@ -21659,7 +21797,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 21659 |
const { t } = useI18n();
|
| 21660 |
const [expanded, setExpanded] = useState(() => isToolCallExpanded(block.toolCallId));
|
| 21661 |
const toggleExpanded = () => {
|
| 21662 |
-
@@ -1146,6 +
|
| 21663 |
setExpanded(next);
|
| 21664 |
};
|
| 21665 |
const inputStr = getWrittenFileText(block) ?? getToolCallInputText(block);
|
|
@@ -21667,7 +21805,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 21667 |
const isStreamingInput = block.rawInput !== undefined;
|
| 21668 |
const isEditTool = isEditToolName(block.toolName);
|
| 21669 |
const resultDiff = result && !result.isError ? getResultDiff(result) : null;
|
| 21670 |
-
@@ -1175,137 +
|
| 21671 |
|| (isApplyPatchToolName(block.toolName) && applyPatchResultHasFailures(result?.details));
|
| 21672 |
const subagent = isSubagentToolDetails(result?.details) ? result.details : null;
|
| 21673 |
const codemodeCallCount = codemode ? codemode.calls.length + codemode.omitted : 0;
|
|
@@ -21916,7 +22054,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 21916 |
interface ResultDiff {
|
| 21917 |
text: string;
|
| 21918 |
}
|
| 21919 |
-
@@ -1316,7 +
|
| 21920 |
return (
|
| 21921 |
<div
|
| 21922 |
style={{
|
|
@@ -21924,7 +22062,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 21924 |
background: "var(--bg)",
|
| 21925 |
}}
|
| 21926 |
>
|
| 21927 |
-
@@ -1325,148 +
|
| 21928 |
);
|
| 21929 |
}
|
| 21930 |
|
|
@@ -22080,7 +22218,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 22080 |
}
|
| 22081 |
|
| 22082 |
function PatchTextView({ text }: { text: string }) {
|
| 22083 |
-
@@ -1483,12 +
|
| 22084 |
const bg =
|
| 22085 |
kind === "added" ? "rgba(34,197,94,0.12)" :
|
| 22086 |
kind === "removed" ? "rgba(248,113,113,0.13)" :
|
|
@@ -22095,7 +22233,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 22095 |
"var(--text)";
|
| 22096 |
|
| 22097 |
return (
|
| 22098 |
-
@@ -1502,7 +
|
| 22099 |
: kind === "removed"
|
| 22100 |
? "3px solid #f87171"
|
| 22101 |
: kind === "hunk"
|
|
@@ -22104,7 +22242,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 22104 |
: "3px solid transparent",
|
| 22105 |
}}
|
| 22106 |
>
|
| 22107 |
-
@@ -1578,16 +
|
| 22108 |
return typeof value === "object" && value !== null && !Array.isArray(value);
|
| 22109 |
}
|
| 22110 |
|
|
@@ -22123,7 +22261,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 22123 |
}}
|
| 22124 |
>
|
| 22125 |
{images.map((image, index) => {
|
| 22126 |
-
@@ -1606,9 +
|
| 22127 |
loading="lazy"
|
| 22128 |
style={{
|
| 22129 |
display: "block",
|
|
@@ -22137,7 +22275,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 22137 |
objectFit: "contain",
|
| 22138 |
border: "1px solid var(--border)",
|
| 22139 |
}}
|
| 22140 |
-
@@ -1620,6 +
|
| 22141 |
);
|
| 22142 |
}
|
| 22143 |
|
|
@@ -22205,7 +22343,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 22205 |
function PairedResult({ text, isEmpty, isError }: {
|
| 22206 |
text: string;
|
| 22207 |
isEmpty: boolean;
|
| 22208 |
-
@@ -1629,8 +
|
| 22209 |
return (
|
| 22210 |
<div
|
| 22211 |
style={{
|
|
@@ -22215,7 +22353,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 22215 |
}}
|
| 22216 |
>
|
| 22217 |
<pre
|
| 22218 |
-
@@ -1668,7 +
|
| 22219 |
<div
|
| 22220 |
style={{
|
| 22221 |
border: "1px solid var(--border)",
|
|
@@ -22224,7 +22362,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 22224 |
overflow: "hidden",
|
| 22225 |
background: "var(--bg)",
|
| 22226 |
}}
|
| 22227 |
-
@@ -1710,9 +
|
| 22228 |
textAlign: "left",
|
| 22229 |
}}
|
| 22230 |
>
|
|
@@ -22235,7 +22373,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 22235 |
<span style={{ minWidth: 0 }}>
|
| 22236 |
<span style={{ display: "block", fontSize: "calc(15px + var(--chat-font-size-offset, 0px))", fontWeight: 700, lineHeight: 1.35 }}>
|
| 22237 |
{t("i18n.conversationCompacted")}
|
| 22238 |
-
@@ -1806,7 +
|
| 22239 |
<div
|
| 22240 |
style={{
|
| 22241 |
border: "1px solid var(--border)",
|
|
@@ -22244,7 +22382,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 22244 |
overflow: "hidden",
|
| 22245 |
background: "var(--bg)",
|
| 22246 |
}}
|
| 22247 |
-
@@ -1852,9 +
|
| 22248 |
</span>
|
| 22249 |
)}
|
| 22250 |
{time && <span style={{ marginLeft: "auto", color: "var(--text-dim)", fontSize: 10, flexShrink: 0 }}>{time}</span>}
|
|
@@ -22255,7 +22393,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 22255 |
</button>
|
| 22256 |
</div>
|
| 22257 |
|
| 22258 |
-
@@ -1872,7 +
|
| 22259 |
<img
|
| 22260 |
src={src}
|
| 22261 |
alt=""
|
|
@@ -22264,7 +22402,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 22264 |
/>
|
| 22265 |
</ImagePreview>
|
| 22266 |
);
|
| 22267 |
-
@@ -1999,6 +
|
| 22268 |
return Object.keys(block.input).every((key) => WRITE_VIEW_KEYS.has(key)) ? content : null;
|
| 22269 |
}
|
| 22270 |
|
|
@@ -22297,7 +22435,7 @@ index f2ff1de..b18cfc6 100644
|
|
| 22297 |
function formatCustomType(type: string): string {
|
| 22298 |
return type || "extension";
|
| 22299 |
}
|
| 22300 |
-
@@ -2021,13 +
|
| 22301 |
return String(first).slice(0, 120);
|
| 22302 |
}
|
| 22303 |
|
|
@@ -22557,7 +22695,7 @@ index ba912ee..0283a47 100644
|
|
| 22557 |
onSelect={() => choose(option)}
|
| 22558 |
star={onSetDefault ? {
|
| 22559 |
diff --git a/components/ModelsConfig.tsx b/components/ModelsConfig.tsx
|
| 22560 |
-
index 9f6b556..
|
| 22561 |
--- a/components/ModelsConfig.tsx
|
| 22562 |
+++ b/components/ModelsConfig.tsx
|
| 22563 |
@@ -54,6 +54,8 @@ import { providerBadgeLabel } from "./enabled-models-helpers";
|
|
@@ -22782,7 +22920,7 @@ index 9f6b556..c1f7dd6 100644
|
|
| 22782 |
Open the verification page and enter this code:
|
| 22783 |
</p>
|
| 22784 |
- <div style={{ padding: "8px 10px", background: "var(--bg)", border: "1px solid var(--border)", borderRadius: 5, color: "var(--text)", fontSize: 16, fontWeight: 700, fontFamily: "var(--font-mono)", letterSpacing: 0 }}>
|
| 22785 |
-
+ <div style={{ padding: "8px 10px", background: "var(--bg)", border: "1px solid var(--border)", borderRadius: "var(--radius-sm)", color: "var(--text)", fontSize: 16, fontWeight: 700, fontFamily: "var(--font-mono)"
|
| 22786 |
{loginState.userCode}
|
| 22787 |
</div>
|
| 22788 |
<p style={{ margin: 0, fontSize: 11, color: "var(--text-dim)", lineHeight: 1.5 }}>
|
|
@@ -30943,7 +31081,7 @@ index 25796d6..51c3f1c 100644
|
|
| 30943 |
- **Notices.** MCP off, with its reason. Otherwise `hostInactive`: an open session whose `/mcp` is another extension's, naming its folder and that extension (only a session can tell). A project folder the route refuses (`cwd-*`) does not hide the global servers: `loadMcpOverview()` loads again without the cwd and the Project group says "Not listed" with the reason. Trust is reported only when the project has a `.pi/mcp.json` or a problem with one: untrusted (`ConfigTrustNotice`), an inherited `false` naming its folder, and inherited trust as "Trusted through <path>", the only place that trust is visible. A folder that requires no trust gets no trust notice unless a decision marks it untrusted: a dangling `.pi/mcp.json` link is listed (`link-dangling`), but the SDK's `existsSync` finds nothing to trust, so Trust would only meet `trust-not-required`. File problems go to the footer (`ConfigFooterStatus`). A load ends after `MCP_OVERVIEW_TIMEOUT_MS` (15 s, both requests together), since Refresh is disabled while one runs.
|
| 30944 |
- **The detail pane shows only what GET sends:** the masked command line (`mcpServerTarget()`), working directory, env and header **names** (each only when set), the `!command` fields and host variables (left out for a refused entry, which never connects; kept for a disabled one), the Sign-in row (**Settings › MCP sign-in** in [mcp-test-sign-in.md](mcp-test-sign-in.md)), the exposure, the file. The Code mode pane holds the choice, the sandbox and `-builtin:codemode`.
|
| 30945 |
diff --git a/docs/agents/sessions.md b/docs/agents/sessions.md
|
| 30946 |
-
index 42e1816..
|
| 30947 |
--- a/docs/agents/sessions.md
|
| 30948 |
+++ b/docs/agents/sessions.md
|
| 30949 |
@@ -38,7 +38,7 @@ On mount `useAgentSession` loads the history, then `GET /api/sessions/[id]/state
|
|
@@ -30960,7 +31098,7 @@ index 42e1816..5869597 100644
|
|
| 30960 |
- Spacing: groups sit right on each other, open or folded (collapsed ones at a 28px pitch); a gap between two of them only pushed the next header away. An 8px spacer row parts the pinned section from the groups (`pinned-spacer`) and the groups from the footer (`footer-spacer`, only when there are groups; a pinned section with no group has the one spacer before the footer).
|
| 30961 |
- Heights (`SIDEBAR_ROW_HEIGHTS`) keep one rhythm: session rows, every header (a project's, the pinned section's, the archive's projects), the empty group's note and the footer's two links are 28px (44px on a phone, a finger's minimum, headers included); only "show more" is a step shorter (26px, 36px). Every control in a row fits it with 2px to spare (⋯ and archive, the rename field and a delete confirmation's buttons 24px, a header's + and ⋯ 22px; 32px on a phone; `SessionTree.test.mjs` checks it), and the drag ghost (22px) stays shorter than a header.
|
| 30962 |
-- Rows: nothing before the title; the right column shows the awaiting-input dot (amber, breathing), else the running spinner, else the unread dot, else the short time (a pinned row's project, an archived row's archive time). A running row keeps its spinner beside ⋯ on hover. Hover and selection are a rounded box inset from both edges; titles are 12px `--text` (the file tree's size), the selected one 500, and the selected row (in the archive view too) has a 3px accent bar on its box's left edge, 7px clear of its top and bottom (a 14px bar; 12px on a phone, 20px), a `::before` of the absolutely positioned row, so its rounded corners never clip it; a delete confirmation's red frame has none. A worktree's branch is meta in the rows' own type (11px dim, a 10px `BranchIcon` kept inline so the ellipsis still cuts the name; the tooltip keeps `⑂`), as a pinned row's project is (no code type). A classic scrollbar takes width on the right only (Chromium's and Safari's styled one is 10px, 6px on a coarse pointer; an overlay one none): `SessionTree` measures it into `--session-tree-scrollbar`, and the right inset shrinks by it, so rows end 6px in or on the gutter's edge, whichever is further in, while the left inset stays 6px (moving it too put the tree's two lines 4px right of everything else's in Chromium). It also puts the width on the sidebar's root (only a tree with a box: not one under the archive view or in a hidden tab), where the files below the sessions read it for their right edge. A row's time (or spinner, dot) ends 4px inside its box, where a folded group's summary ends: neither the row nor its always-rendered actions span has a gap, each shown action brings its own 2px (`margin-left`), so the hidden ones leave the time on the edge. The list keeps the scrollbar's room even when everything fits (`scrollbar-gutter: stable`; `overflow-y: scroll` in Safari before 18.2), so rows never narrow when it starts to scroll.
|
| 30963 |
-
+- Rows: nothing before the title; the right column shows the awaiting-input dot (amber, breathing), else the running spinner, else the unread dot, else the short time (a pinned row's project, an archived row's archive time). A running row keeps its spinner beside ⋯ on hover. A live row has no hover Archive button (Archive is in its ⋯ menu); the archive view's rows keep Restore one click away. The toolbar's search opens the command bar (`components/SearchCommandBar.tsx`, also ⌘K / Ctrl+K outside a terminal): recent top-level conversations until something is typed, then `/api/sessions/search` matches with their text; arrows move, Enter opens, Esc closes. Hover and selection are a rounded box inset from both edges; titles are 12px `--text` (the file tree's size), the selected one 500, and the selected row (in the archive view too) has a 3px accent bar on its box's left edge, 7px clear of its top and bottom (a 14px bar; 12px on a phone, 20px), a `::before` of the absolutely positioned row, so its rounded corners never clip it; a delete confirmation's red frame has none. A worktree's branch is meta in the rows' own type (11px dim, a 10px `BranchIcon` kept inline so the ellipsis still cuts the name; the tooltip keeps `⑂`), as a pinned row's project is (no code type). A classic scrollbar takes width on the right only (Chromium's and Safari's styled one is 10px, 6px on a coarse pointer; an overlay one none): `SessionTree` measures it into `--session-tree-scrollbar`, and the right inset shrinks by it, so rows end 6px in or on the gutter's edge, whichever is further in, while the left inset stays 6px (moving it too put the tree's two lines 4px right of everything else's in Chromium). It also puts the width on the sidebar's root (only a tree with a box: not one under the archive view or in a hidden tab), where the files below the sessions read it for their right edge. A row's time (or spinner, dot) ends 4px inside its box, where a folded group's summary ends: neither the row nor its always-rendered actions span has a gap, each shown action brings its own 2px (`margin-left`), so the hidden ones leave the time on the edge. The list keeps the scrollbar's room even when everything fits (`scrollbar-gutter: stable`; `overflow-y: scroll` in Safari before 18.2), so rows never narrow when it starts to scroll.
|
| 30964 |
- Row keys are unique and stable (`session:<context>:<rootId>`, `group:<key>`, `more:<key>`, `empty:<key>`, and fixed keys for the pinned header, the two spacers (`pinned-spacer`, `footer-spacer`) and the footer); focus tracking and `keepMounted` go by them.
|
| 30965 |
- Menus (`SidebarMenu`) and the toast live in `SessionSidebar`, above the virtualized rows, so a row scrolled away keeps its open menu (the project/worktree picker of the files tab and of the composer's bar uses `SidebarMenu` for its own two menus); the row with focus, a menu, a rename or a delete confirmation stays mounted (`keepMounted`). Menus are portaled to `document.body`: the mobile drawer's `transform` would pin a `position: fixed` menu to the drawer. The archive view is a second tree; the main one stays mounted underneath, the two in one grid cell (`.sidebar-sessions-views`, `grid-area: 1 / 1`; the archive opaque in `--bg-panel` and stacked above the tree for good, `position: relative; z-index: 1`: the tree's rows are positioned and paint over an unpositioned view, which only its animation lifted, so the frame its entrance ended in flashed the tree). Going in and out moves, in both layouts and on phones (`useSidebarMotion()`, `archiveViewState()` in `hooks/useSidebarMotion.ts`; `setArchiveView()` starts it on a change only, and View, Back and a fork's open all go through it): in, the archive slides from 14px right and fades in over the tree (`.is-entering`, 180ms ease-out), the tree `inert` under it until the view is in, then `hidden` as before; out, the tree is back at once and the archive slides and fades out to the right over it (`.is-leaving`, 180ms ease-in, `forwards`), still mounted with its rows (`archiveRows` is built while `archiveMounted`, else it would leave empty), `inert` and `pointer-events: none`, so nothing in it is clicked twice and a click lands on the tree, until its own `animationend` (not one bubbling from a row) unmounts it. A phase that gets no end event (a sidebar not shown: a hidden tab, a closed drawer) ends 300 ms after its start (`SIDEBAR_MOTION_MS` + `SIDEBAR_MOTION_GRACE_MS`). Under `prefers-reduced-motion` no phase starts and the CSS has no animation: the views swap at once, as before.
|
| 30966 |
- Focus: a menu returns it to the opener it was given when it opened (the parent drops `returnFocusTo` with the menu); a row's ⋯ or a group's buttons are `display: none` by then unless hovered, selected or focused, so `menuFocusReturnTarget()` falls back to the row's first focusable control (its main link or transient-session button, a group's toggle). Controls that go away under the keyboard hand focus on: the archive's Back button when it opens, the footer link (or the selected tab, or with no tabs the toolbar row's search) after Back, the family's row after a toast's Undo or a delete Cancel (`focusIfLost()`, only when focus fell to `<body>`). The archive's two moves make the view they leave `inert` before it is hidden or gone, and Chromium blurs an inert element only at its next frame, after the effect that moves focus has run: `focusIfHidden()` counts focus on something without a box or inside `[inert]` as lost too.
|
|
@@ -43686,7 +43824,7 @@ index 0000000..7b6ea92
|
|
| 43686 |
+ </g>
|
| 43687 |
+</svg>
|
| 43688 |
diff --git a/public/offline.html b/public/offline.html
|
| 43689 |
-
index 0dac4b2..
|
| 43690 |
--- a/public/offline.html
|
| 43691 |
+++ b/public/offline.html
|
| 43692 |
@@ -3,14 +3,38 @@
|
|
@@ -43745,7 +43883,7 @@ index 0dac4b2..e07421e 100644
|
|
| 43745 |
}
|
| 43746 |
|
| 43747 |
main {
|
| 43748 |
-
@@ -30,53 +58,
|
| 43749 |
text-align: center;
|
| 43750 |
}
|
| 43751 |
|
|
@@ -43768,7 +43906,6 @@ index 0dac4b2..e07421e 100644
|
|
| 43768 |
+ margin: 0 0 6px;
|
| 43769 |
+ font-size: 18px;
|
| 43770 |
+ font-weight: 600;
|
| 43771 |
-
+ letter-spacing: -0.01em;
|
| 43772 |
+ line-height: 1.3;
|
| 43773 |
}
|
| 43774 |
|
|
|
|
| 3977 |
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
| 3978 |
+OTHER DEALINGS IN THE FONT SOFTWARE.
|
| 3979 |
diff --git a/app/globals.css b/app/globals.css
|
| 3980 |
+
index d6b5535..14749e0 100644
|
| 3981 |
--- a/app/globals.css
|
| 3982 |
+++ b/app/globals.css
|
| 3983 |
@@ -24,46 +24,109 @@
|
|
|
|
| 4235 |
}
|
| 4236 |
|
| 4237 |
/* Theme toggle: circular wipe via View Transitions API.
|
| 4238 |
+
@@ -218,16 +301,15 @@ body {
|
| 4239 |
margin-bottom: 22px;
|
| 4240 |
}
|
| 4241 |
|
|
|
|
| 4247 |
}
|
| 4248 |
|
| 4249 |
.web-login-brand h1 {
|
| 4250 |
+
margin: 0 0 3px;
|
| 4251 |
+
color: var(--text);
|
| 4252 |
+
font-size: 22px;
|
| 4253 |
+
- letter-spacing: 0;
|
| 4254 |
+
}
|
| 4255 |
+
|
| 4256 |
+
.web-login-brand p {
|
| 4257 |
+
@@ -247,15 +329,15 @@ body {
|
| 4258 |
min-width: 0;
|
| 4259 |
padding: 10px 10px 10px 14px;
|
| 4260 |
border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
|
|
|
|
| 4273 |
}
|
| 4274 |
|
| 4275 |
.web-login-label {
|
| 4276 |
+
@@ -307,14 +389,13 @@ html.dark .web-login-error {
|
| 4277 |
min-height: 32px;
|
| 4278 |
padding: 7px 14px;
|
| 4279 |
border: none;
|
|
|
|
| 4282 |
background: var(--accent);
|
| 4283 |
color: var(--accent-contrast);
|
| 4284 |
cursor: pointer;
|
| 4285 |
+
font: inherit;
|
| 4286 |
+
font-size: 13px;
|
| 4287 |
+
font-weight: 600;
|
| 4288 |
+
- letter-spacing: 0;
|
| 4289 |
+
}
|
| 4290 |
+
|
| 4291 |
+
.web-login-form button:not(:disabled):hover {
|
| 4292 |
+
@@ -322,7 +403,7 @@ html.dark .web-login-error {
|
| 4293 |
}
|
| 4294 |
|
| 4295 |
.web-login-form button:focus-visible {
|
|
|
|
| 4298 |
outline-offset: 2px;
|
| 4299 |
}
|
| 4300 |
|
| 4301 |
+
@@ -332,110 +413,139 @@ html.dark .web-login-error {
|
| 4302 |
cursor: not-allowed;
|
| 4303 |
}
|
| 4304 |
|
|
|
|
| 4394 |
overflow: hidden;
|
| 4395 |
- text-overflow: ellipsis;
|
| 4396 |
- white-space: nowrap;
|
| 4397 |
+
}
|
| 4398 |
-
|
| 4399 |
-.terminal-status-dot {
|
| 4400 |
- width: 7px;
|
|
|
|
| 4421 |
- color: #9ca3af;
|
| 4422 |
- cursor: pointer;
|
| 4423 |
- font: inherit;
|
| 4424 |
+
-}
|
| 4425 |
-
|
| 4426 |
-.terminal-panel-exit {
|
| 4427 |
- padding: 7px 12px;
|
|
|
|
| 4519 |
}
|
| 4520 |
|
| 4521 |
.terminal-xterm:has(+ .terminal-extra-keys) {
|
| 4522 |
+
@@ -451,7 +561,7 @@ html[data-keyboard-open] .terminal-extra-keys {
|
| 4523 |
padding: 0;
|
| 4524 |
border: none;
|
| 4525 |
background: transparent;
|
|
|
|
| 4528 |
font-family: var(--font-mono);
|
| 4529 |
font-size: 12px;
|
| 4530 |
touch-action: manipulation;
|
| 4531 |
+
@@ -462,7 +572,7 @@ html[data-keyboard-open] .terminal-extra-keys {
|
| 4532 |
}
|
| 4533 |
|
| 4534 |
.terminal-extra-keys button:active {
|
|
|
|
| 4537 |
}
|
| 4538 |
|
| 4539 |
.terminal-extra-keys .is-rotated {
|
| 4540 |
+
@@ -471,17 +581,10 @@ html[data-keyboard-open] .terminal-extra-keys {
|
| 4541 |
}
|
| 4542 |
|
| 4543 |
.terminal-extra-keys button.is-active {
|
|
|
|
| 4558 |
|
| 4559 |
/*
|
| 4560 |
* Chromium-only scrollbars. Firefox renders its native ones, which the standard
|
| 4561 |
+
@@ -502,7 +605,7 @@ html[data-keyboard-open] .terminal-extra-keys {
|
| 4562 |
background-color: color-mix(in srgb, var(--text-dim) 45%, transparent);
|
| 4563 |
background-clip: padding-box;
|
| 4564 |
border: 2px solid transparent;
|
|
|
|
| 4567 |
}
|
| 4568 |
/* Only ever set background-color here: the `background` shorthand resets
|
| 4569 |
background-clip and would snap the thumb to the full 10px on hover. */
|
| 4570 |
+
@@ -538,21 +641,10 @@ html[data-keyboard-open] .terminal-extra-keys {
|
| 4571 |
}
|
| 4572 |
::-webkit-scrollbar-thumb {
|
| 4573 |
border-width: 1px;
|
|
|
|
| 4590 |
|
| 4591 |
|
| 4592 |
.extension-status-shelf {
|
| 4593 |
+
@@ -686,8 +778,9 @@ button.extension-widget-trigger:hover,
|
| 4594 |
}
|
| 4595 |
|
| 4596 |
button.extension-widget-trigger:focus-visible {
|
|
|
|
| 4602 |
}
|
| 4603 |
|
| 4604 |
.extension-widget-placement {
|
| 4605 |
+
@@ -703,9 +796,9 @@ button.extension-widget-trigger:focus-visible {
|
| 4606 |
|
| 4607 |
.extension-widget-placement-icon {
|
| 4608 |
display: block;
|
|
|
|
| 4615 |
fill: currentColor;
|
| 4616 |
}
|
| 4617 |
|
| 4618 |
+
@@ -803,8 +896,9 @@ button.extension-widget-trigger:focus-visible {
|
| 4619 |
}
|
| 4620 |
|
| 4621 |
.extension-status-command:focus-visible {
|
|
|
|
| 4627 |
}
|
| 4628 |
|
| 4629 |
.extension-status-command:disabled {
|
| 4630 |
+
@@ -826,8 +920,8 @@ button.extension-widget-trigger:focus-visible {
|
| 4631 |
}
|
| 4632 |
|
| 4633 |
:root {
|
|
|
|
| 4638 |
--font-ui: var(--font-ui-default);
|
| 4639 |
--font-mono: var(--font-mono-default);
|
| 4640 |
--font-ui-weight: 400;
|
| 4641 |
+
@@ -866,7 +960,6 @@ pre, code {
|
| 4642 |
+
margin: 10px 0 5px;
|
| 4643 |
+
color: var(--text);
|
| 4644 |
+
line-height: 1.35;
|
| 4645 |
+
- letter-spacing: 0;
|
| 4646 |
+
}
|
| 4647 |
+
.markdown-body h1 { font-size: 1.16em; }
|
| 4648 |
+
.markdown-body h2 { font-size: 1.08em; }
|
| 4649 |
+
@@ -883,11 +976,11 @@ pre, code {
|
| 4650 |
.markdown-body ol > li { list-style-type: decimal; }
|
| 4651 |
.markdown-body li > p { margin: 0; }
|
| 4652 |
.markdown-body li::marker {
|
|
|
|
| 4661 |
font-weight: 700;
|
| 4662 |
}
|
| 4663 |
.markdown-body em { color: var(--text-muted); }
|
| 4664 |
+
@@ -896,24 +989,23 @@ pre, code {
|
| 4665 |
text-decoration: underline;
|
| 4666 |
text-decoration-thickness: 1px;
|
| 4667 |
text-underline-offset: 3px;
|
|
|
|
| 4690 |
}
|
| 4691 |
.markdown-body .contains-task-list {
|
| 4692 |
padding-left: 0;
|
| 4693 |
+
@@ -935,7 +1027,7 @@ pre, code {
|
| 4694 |
height: 14px;
|
| 4695 |
margin: 0;
|
| 4696 |
border: 1px solid var(--border);
|
|
|
|
| 4699 |
background: var(--bg);
|
| 4700 |
display: inline-grid;
|
| 4701 |
place-content: center;
|
| 4702 |
+
@@ -967,8 +1059,8 @@ pre, code {
|
| 4703 |
margin: 8px 0;
|
| 4704 |
overflow-x: auto;
|
| 4705 |
overflow-y: hidden;
|
|
|
|
| 4710 |
}
|
| 4711 |
.markdown-body table {
|
| 4712 |
border-collapse: separate;
|
| 4713 |
+
@@ -1088,7 +1180,7 @@ pre, code {
|
| 4714 |
padding: 7px 8px;
|
| 4715 |
overflow: auto;
|
| 4716 |
border: 1px solid var(--border);
|
|
|
|
| 4719 |
background: var(--bg-panel);
|
| 4720 |
font-family: var(--font-mono);
|
| 4721 |
font-size: 11px;
|
| 4722 |
+
@@ -1108,16 +1200,13 @@ pre, code {
|
| 4723 |
min-width: 0;
|
| 4724 |
max-width: 100%;
|
| 4725 |
margin: 6px 0;
|
|
|
|
| 4739 |
color: var(--text-dim);
|
| 4740 |
font-size: 11px;
|
| 4741 |
display: flex;
|
| 4742 |
+
@@ -1137,7 +1226,7 @@ pre, code {
|
| 4743 |
}
|
| 4744 |
.markdown-code-action {
|
| 4745 |
border: 1px solid var(--border);
|
|
|
|
| 4748 |
padding: 2px 7px;
|
| 4749 |
background: var(--bg);
|
| 4750 |
color: var(--text-muted);
|
| 4751 |
+
@@ -1191,8 +1280,9 @@ pre, code {
|
| 4752 |
opacity: 0.85;
|
| 4753 |
}
|
| 4754 |
.mermaid-preview-button:focus-visible {
|
|
|
|
| 4760 |
}
|
| 4761 |
|
| 4762 |
.image-preview-dialog {
|
| 4763 |
+
@@ -1228,7 +1318,7 @@ pre, code {
|
| 4764 |
max-width: 100%;
|
| 4765 |
max-height: 100%;
|
| 4766 |
object-fit: contain;
|
|
|
|
| 4769 |
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
|
| 4770 |
}
|
| 4771 |
|
| 4772 |
+
@@ -1244,7 +1334,7 @@ pre, code {
|
| 4773 |
height: 36px;
|
| 4774 |
padding: 0;
|
| 4775 |
border: 1px solid var(--border);
|
|
|
|
| 4778 |
background: var(--bg-panel);
|
| 4779 |
color: var(--text-muted);
|
| 4780 |
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.28);
|
| 4781 |
+
@@ -1259,7 +1349,7 @@ pre, code {
|
| 4782 |
}
|
| 4783 |
|
| 4784 |
.image-preview-close:focus-visible {
|
|
|
|
| 4787 |
outline-offset: 2px;
|
| 4788 |
}
|
| 4789 |
|
| 4790 |
+
@@ -1320,7 +1410,7 @@ pre, code {
|
| 4791 |
height: 24px;
|
| 4792 |
overflow: hidden;
|
| 4793 |
border: 1px solid var(--border);
|
|
|
|
| 4796 |
background: var(--bg);
|
| 4797 |
}
|
| 4798 |
.mermaid-zoom-stepper button,
|
| 4799 |
+
@@ -1347,7 +1437,7 @@ pre, code {
|
| 4800 |
.mermaid-zoom-icon-button {
|
| 4801 |
flex: 0 0 24px;
|
| 4802 |
border: 1px solid var(--border);
|
|
|
|
| 4805 |
}
|
| 4806 |
.mermaid-zoom-stepper button:hover:not(:disabled),
|
| 4807 |
.mermaid-zoom-icon-button:hover {
|
| 4808 |
+
@@ -1360,8 +1450,9 @@ pre, code {
|
| 4809 |
}
|
| 4810 |
.mermaid-zoom-stepper button:focus-visible,
|
| 4811 |
.mermaid-zoom-icon-button:focus-visible {
|
|
|
|
| 4817 |
}
|
| 4818 |
.mermaid-zoom-value {
|
| 4819 |
min-width: 48px;
|
| 4820 |
+
@@ -1384,7 +1475,7 @@ pre, code {
|
| 4821 |
.mermaid-zoom-canvas {
|
| 4822 |
padding: 12px;
|
| 4823 |
border: 1px solid var(--border);
|
|
|
|
| 4826 |
background: var(--bg-panel);
|
| 4827 |
margin: 0 auto;
|
| 4828 |
transition: width 0.15s ease;
|
| 4829 |
+
@@ -1460,7 +1551,7 @@ pre, code {
|
| 4830 |
height: 24px;
|
| 4831 |
overflow: hidden;
|
| 4832 |
border: 1px solid var(--border);
|
|
|
|
| 4835 |
}
|
| 4836 |
|
| 4837 |
.file-viewer-mode-button {
|
| 4838 |
+
@@ -1471,7 +1562,6 @@ pre, code {
|
| 4839 |
+
font: inherit;
|
| 4840 |
+
font-size: 11px;
|
| 4841 |
+
font-weight: 500;
|
| 4842 |
+
- letter-spacing: 0;
|
| 4843 |
+
cursor: pointer;
|
| 4844 |
+
white-space: nowrap;
|
| 4845 |
+
}
|
| 4846 |
+
@@ -1503,12 +1593,11 @@ pre, code {
|
| 4847 |
height: 32px;
|
| 4848 |
padding: 0;
|
| 4849 |
box-sizing: border-box;
|
|
|
|
| 4858 |
opacity: 0;
|
| 4859 |
visibility: hidden;
|
| 4860 |
pointer-events: none;
|
| 4861 |
+
@@ -1542,7 +1631,7 @@ pre, code {
|
| 4862 |
}
|
| 4863 |
|
| 4864 |
.chat-scroll-to-bottom:focus-visible {
|
|
|
|
| 4867 |
outline-offset: 2px;
|
| 4868 |
}
|
| 4869 |
|
| 4870 |
+
@@ -1566,11 +1655,13 @@ pre, code {
|
| 4871 |
}
|
| 4872 |
}
|
| 4873 |
|
|
|
|
| 4885 |
margin: 0 auto;
|
| 4886 |
}
|
| 4887 |
|
| 4888 |
+
@@ -1584,22 +1675,20 @@ pre, code {
|
| 4889 |
|
| 4890 |
.new-session-logo {
|
| 4891 |
flex: none;
|
|
|
|
| 4910 |
+ color: var(--text-strong);
|
| 4911 |
+ font-size: 20px;
|
| 4912 |
+ font-weight: 600;
|
|
|
|
| 4913 |
line-height: 1.15;
|
| 4914 |
white-space: nowrap;
|
| 4915 |
}
|
| 4916 |
+
@@ -1613,7 +1702,8 @@ pre, code {
|
| 4917 |
gap: 6px;
|
| 4918 |
min-width: 0;
|
| 4919 |
color: var(--text-dim);
|
|
|
|
| 4923 |
line-height: 1.4;
|
| 4924 |
white-space: nowrap;
|
| 4925 |
}
|
| 4926 |
+
@@ -1638,7 +1728,7 @@ pre, code {
|
| 4927 |
gap: 3px;
|
| 4928 |
margin: -3px -4px;
|
| 4929 |
padding: 3px 4px;
|
|
|
|
| 4932 |
color: var(--accent);
|
| 4933 |
font-weight: 600;
|
| 4934 |
text-decoration: none;
|
| 4935 |
+
@@ -1652,38 +1742,27 @@ pre, code {
|
| 4936 |
|
| 4937 |
/* The ring inside the box, clear of the "→" and "·" beside it. */
|
| 4938 |
.new-session-update:focus-visible {
|
|
|
|
| 4977 |
}
|
| 4978 |
}
|
| 4979 |
|
| 4980 |
+
@@ -1700,8 +1779,8 @@ pre, code {
|
| 4981 |
tab's stacked boxes, the row below the sessions), as the sidebar drew it
|
| 4982 |
on main: two grey boxes in the header buttons' material, 11px, the
|
| 4983 |
project's whole path and the worktree's branch in code type, cut at their
|
|
|
|
| 4988 |
.project-picker {
|
| 4989 |
display: flex;
|
| 4990 |
align-items: center;
|
| 4991 |
+
@@ -1722,52 +1801,14 @@ pre, code {
|
| 4992 |
min-width: 0;
|
| 4993 |
height: 29px;
|
| 4994 |
padding: 0 10px;
|
|
|
|
| 5044 |
}
|
| 5045 |
|
| 5046 |
/* The project's name (its display name, else its folder's), cut at its
|
| 5047 |
+
@@ -1781,8 +1822,9 @@ pre, code {
|
| 5048 |
max-width: 28ch;
|
| 5049 |
overflow: hidden;
|
| 5050 |
color: var(--text-dim);
|
|
|
|
| 5055 |
}
|
| 5056 |
|
| 5057 |
.project-picker-parent {
|
| 5058 |
+
@@ -1800,10 +1842,6 @@ pre, code {
|
| 5059 |
text-overflow: ellipsis;
|
| 5060 |
}
|
| 5061 |
|
|
|
|
| 5066 |
.project-picker-button:hover,
|
| 5067 |
.project-picker-button[aria-expanded="true"] {
|
| 5068 |
background: var(--bg-selected);
|
| 5069 |
+
@@ -1811,8 +1849,9 @@ pre, code {
|
| 5070 |
}
|
| 5071 |
|
| 5072 |
.project-picker-button:focus-visible {
|
|
|
|
| 5078 |
}
|
| 5079 |
|
| 5080 |
.project-picker-icon,
|
| 5081 |
+
@@ -1851,7 +1890,8 @@ pre, code {
|
| 5082 |
path's color and code type, cut at its end. */
|
| 5083 |
.project-picker-label.is-alias {
|
| 5084 |
color: var(--text);
|
|
|
|
| 5088 |
}
|
| 5089 |
|
| 5090 |
/* A path or a branch, cut at its left (rtl), the text itself kept left to
|
| 5091 |
+
@@ -1901,7 +1941,8 @@ pre, code {
|
| 5092 |
border-color: color-mix(in srgb, var(--accent) 40%, transparent);
|
| 5093 |
background: color-mix(in srgb, var(--accent) 6%, transparent);
|
| 5094 |
color: var(--text-dim);
|
|
|
|
| 5098 |
}
|
| 5099 |
|
| 5100 |
/* No worktree list here (the files tab): a box that says why, and does nothing. */
|
| 5101 |
+
@@ -1978,7 +2019,7 @@ pre, code {
|
| 5102 |
box-sizing: border-box;
|
| 5103 |
flex: 0 0 24px;
|
| 5104 |
border: 1px solid var(--border);
|
|
|
|
| 5107 |
background: transparent;
|
| 5108 |
color: var(--text-muted);
|
| 5109 |
cursor: pointer;
|
| 5110 |
+
@@ -2003,8 +2044,9 @@ pre, code {
|
| 5111 |
|
| 5112 |
.file-viewer-mode-button:focus-visible,
|
| 5113 |
.file-viewer-icon-button:focus-visible {
|
|
|
|
| 5119 |
}
|
| 5120 |
|
| 5121 |
.directory-picker-path:focus-visible {
|
| 5122 |
+
@@ -2037,7 +2079,7 @@ pre, code {
|
| 5123 |
.markdown-file-preview pre {
|
| 5124 |
background: var(--bg-panel);
|
| 5125 |
border: 1px solid var(--border);
|
|
|
|
| 5128 |
padding: 12px 16px;
|
| 5129 |
overflow-x: auto;
|
| 5130 |
font-family: var(--font-mono);
|
| 5131 |
+
@@ -2049,7 +2091,7 @@ pre, code {
|
| 5132 |
font-size: 0.9em;
|
| 5133 |
background: var(--bg-panel);
|
| 5134 |
padding: 1px 5px;
|
|
|
|
| 5137 |
}
|
| 5138 |
.markdown-file-preview pre code {
|
| 5139 |
background: none;
|
| 5140 |
+
@@ -2061,7 +2103,7 @@ pre, code {
|
| 5141 |
margin-bottom: 20px;
|
| 5142 |
padding: 14px 16px 12px;
|
| 5143 |
border: 1px solid var(--border);
|
|
|
|
| 5146 |
background: var(--bg-panel);
|
| 5147 |
}
|
| 5148 |
.markdown-frontmatter-title {
|
| 5149 |
+
@@ -2114,18 +2156,6 @@ pre, code {
|
| 5150 |
text-underline-offset: 2px;
|
| 5151 |
}
|
| 5152 |
|
|
|
|
| 5165 |
@keyframes drop-zone-in {
|
| 5166 |
from { opacity: 0; transform: scale(0.97); }
|
| 5167 |
to { opacity: 1; transform: scale(1); }
|
| 5168 |
+
@@ -2230,10 +2260,6 @@ span.token.table {
|
| 5169 |
.panel-resize-handle.is-resizing::after {
|
| 5170 |
background: color-mix(in srgb, var(--text-muted) 70%, var(--border));
|
| 5171 |
}
|
|
|
|
| 5176 |
/* Desktop sidebar: animate width */
|
| 5177 |
@media (min-width: 641px) {
|
| 5178 |
.sidebar-overlay-backdrop {
|
| 5179 |
+
@@ -2264,119 +2290,6 @@ span.token.table {
|
| 5180 |
}
|
| 5181 |
}
|
| 5182 |
|
|
|
|
| 5296 |
/* Mobile sidebar: slide in/out as overlay */
|
| 5297 |
@media (max-width: 640px) {
|
| 5298 |
.chat-stats-center {
|
| 5299 |
+
@@ -2401,31 +2314,7 @@ span.token.table {
|
| 5300 |
box-shadow: none;
|
| 5301 |
}
|
| 5302 |
|
|
|
|
| 5329 |
display: none;
|
| 5330 |
}
|
| 5331 |
.directory-picker-back,
|
| 5332 |
+
@@ -2474,3 +2363,490 @@ span.token.table {
|
| 5333 |
display: none !important;
|
| 5334 |
}
|
| 5335 |
}
|
|
|
|
| 5497 |
+ transition: opacity 0.12s;
|
| 5498 |
+}
|
| 5499 |
+:is(.turn-user, .turn-reply):is(:hover, :focus-within) .message-actions-reveal,
|
| 5500 |
+
+.message-actions-reveal.is-pinned,
|
| 5501 |
+
+.message-actions-reveal:has([data-done]) { opacity: 1; }
|
| 5502 |
+
+/* No hover on a touch screen: the actions stay in view there. */
|
| 5503 |
+
+@media (hover: none) { .message-actions-reveal { opacity: 1; } }
|
| 5504 |
+.message-meta {
|
| 5505 |
+ padding: 0 6px;
|
| 5506 |
+ color: var(--text-dim);
|
|
|
|
| 5508 |
+ font-variant-numeric: tabular-nums;
|
| 5509 |
+ white-space: nowrap;
|
| 5510 |
+}
|
| 5511 |
+
+/* Vertical rhythm. Steps (thoughts, tool calls, a step's text) are one 4px rhythm,
|
| 5512 |
+
+ within a message and from one message to the next, folded under their summary or
|
| 5513 |
+
+ not (a running turn's are not yet); the answer sits 8px under its steps, open or
|
| 5514 |
+
+ closed; its paragraphs keep the reading gap; a turn ends 22px before the next. */
|
| 5515 |
+.turn-reply { margin: 0 0 22px; }
|
| 5516 |
+.turn-reply > .message-actions { margin: 4px 0 0 -6px; }
|
| 5517 |
+.turn-reply.is-step { margin: 0; }
|
| 5518 |
+
+/* Each message sits in a wrapper of its own (ChatWindow): the rhythm is set between those. */
|
| 5519 |
+
+:is(.turn-reply.is-step, :has(> .turn-reply.is-step)) + :is(.turn-reply.is-step, :has(> .turn-reply.is-step)) { margin-top: 4px; }
|
| 5520 |
+
+:is(.turn-reply.is-step, :has(> .turn-reply.is-step)) + :is(.turn-reply:not(.is-step), :has(> .turn-reply:not(.is-step))) { margin-top: 8px; }
|
| 5521 |
+.reply-blocks { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
| 5522 |
+
+.turn-reply.is-step .reply-blocks { gap: 4px; }
|
| 5523 |
+
|
| 5524 |
+/* The steps of a turn: one summary line, "Ran 2 commands, read a file", that
|
| 5525 |
+ opens onto the steps themselves along a thin rail. */
|
|
|
|
| 5551 |
+ position: relative;
|
| 5552 |
+ display: flex;
|
| 5553 |
+ flex-direction: column;
|
| 5554 |
+
+ gap: 0;
|
| 5555 |
+
+ margin: 4px 0 0 1px;
|
| 5556 |
+ padding-left: 16px;
|
| 5557 |
+}
|
| 5558 |
+/* A drawing, not a border: the rail the steps hang from. */
|
|
|
|
| 5893 |
<script
|
| 5894 |
dangerouslySetInnerHTML={{
|
| 5895 |
__html: THEME_INIT_SCRIPT,
|
| 5896 |
+
diff --git a/app/letter-spacing.test.mjs b/app/letter-spacing.test.mjs
|
| 5897 |
+
new file mode 100644
|
| 5898 |
+
index 0000000..099fc49
|
| 5899 |
+
--- /dev/null
|
| 5900 |
+
+++ b/app/letter-spacing.test.mjs
|
| 5901 |
+
@@ -0,0 +1,35 @@
|
| 5902 |
+
+import assert from "node:assert/strict";
|
| 5903 |
+
+import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
| 5904 |
+
+import test from "node:test";
|
| 5905 |
+
+
|
| 5906 |
+
+// The design system's rule: never tweak letter spacing. Every font keeps its natural spacing,
|
| 5907 |
+
+// so no stylesheet or component sets letter-spacing (not even 0, which only ever undid a tweak).
|
| 5908 |
+
+const PATTERN = /letter-spacing|letterSpacing|\btracking-(?:tighter|tight|normal|wide|wider|widest|\[)/;
|
| 5909 |
+
+
|
| 5910 |
+
+function files(dir, keep) {
|
| 5911 |
+
+ const out = [];
|
| 5912 |
+
+ for (const name of readdirSync(dir)) {
|
| 5913 |
+
+ if (name === "node_modules" || name.startsWith(".")) continue;
|
| 5914 |
+
+ const path = `${dir}/${name}`;
|
| 5915 |
+
+ if (statSync(path).isDirectory()) out.push(...files(path, keep));
|
| 5916 |
+
+ else if (keep(name)) out.push(path);
|
| 5917 |
+
+ }
|
| 5918 |
+
+ return out;
|
| 5919 |
+
+}
|
| 5920 |
+
+
|
| 5921 |
+
+test("nothing in Pi Web tweaks letter spacing", () => {
|
| 5922 |
+
+ const root = new URL("..", import.meta.url).pathname.replace(/\/$/, "");
|
| 5923 |
+
+ const ours = [
|
| 5924 |
+
+ ...["app", "components", "lib", "hooks"].flatMap((dir) => files(`${root}/${dir}`, (n) => /\.(css|tsx?|mjs)$/.test(n) && !n.endsWith(".test.mjs"))),
|
| 5925 |
+
+ `${root}/public/offline.html`,
|
| 5926 |
+
+ ];
|
| 5927 |
+
+ // The Hugging Face gateway's pages and the design system's sources, where this checkout sits beside them.
|
| 5928 |
+
+ for (const dir of ["../space/gateway/static", "../design"]) {
|
| 5929 |
+
+ if (existsSync(`${root}/${dir}`)) ours.push(...readdirSync(`${root}/${dir}`).filter((n) => /\.(css|html|js|json|py)$/.test(n)).map((n) => `${root}/${dir}/${n}`));
|
| 5930 |
+
+ }
|
| 5931 |
+
+ for (const path of ours) {
|
| 5932 |
+
+ readFileSync(path, "utf8").split("\n").forEach((line, index) => {
|
| 5933 |
+
+ assert.doesNotMatch(line, PATTERN, `${path.replace(root + "/", "")}:${index + 1}`);
|
| 5934 |
+
+ });
|
| 5935 |
+
+ }
|
| 5936 |
+
+});
|
| 5937 |
diff --git a/app/login/page.tsx b/app/login/page.tsx
|
| 5938 |
index 80264fe..6506201 100644
|
| 5939 |
--- a/app/login/page.tsx
|
|
|
|
| 5986 |
lang: "en",
|
| 5987 |
icons: [
|
| 5988 |
diff --git a/app/settings.css b/app/settings.css
|
| 5989 |
+
index 0bf21eb..d2f4031 100644
|
| 5990 |
--- a/app/settings.css
|
| 5991 |
+++ b/app/settings.css
|
| 5992 |
@@ -35,14 +35,15 @@
|
|
|
|
| 6061 |
.config-sidebar-group-label {
|
| 6062 |
display: flex;
|
| 6063 |
align-items: center;
|
| 6064 |
+
@@ -129,11 +133,9 @@
|
| 6065 |
padding: 4px 8px 3px;
|
| 6066 |
color: var(--text-dim);
|
| 6067 |
font-family: inherit;
|
|
|
|
| 6070 |
+ font-size: 12px;
|
| 6071 |
+ font-weight: 400;
|
| 6072 |
line-height: 1.4;
|
| 6073 |
+
- letter-spacing: 0;
|
| 6074 |
- text-transform: uppercase;
|
| 6075 |
}
|
| 6076 |
|
| 6077 |
.config-sidebar-group-label-text {
|
| 6078 |
+
@@ -144,6 +146,13 @@
|
| 6079 |
white-space: nowrap;
|
| 6080 |
}
|
| 6081 |
|
|
|
|
| 6089 |
/* Sits in the heading row, so the sidebar gains no height for it. */
|
| 6090 |
.config-sidebar-group-switch {
|
| 6091 |
display: inline-flex;
|
| 6092 |
+
@@ -153,8 +162,9 @@
|
| 6093 |
}
|
| 6094 |
|
| 6095 |
.config-sidebar-group-count {
|
|
|
|
| 6100 |
}
|
| 6101 |
|
| 6102 |
/* Three lines, then scroll: the phone layout gives the whole sidebar 190px. */
|
| 6103 |
+
@@ -188,7 +198,7 @@
|
| 6104 |
height: 30px;
|
| 6105 |
padding: 0 8px;
|
| 6106 |
border: 0;
|
|
|
|
| 6109 |
outline: none;
|
| 6110 |
background: transparent;
|
| 6111 |
color: var(--text);
|
| 6112 |
+
@@ -202,17 +212,19 @@
|
| 6113 |
|
| 6114 |
.config-sidebar-item:not(:disabled):hover,
|
| 6115 |
.config-sidebar-item:not(:disabled):focus-visible {
|
|
|
|
| 6134 |
}
|
| 6135 |
|
| 6136 |
.config-sidebar-item:disabled {
|
| 6137 |
+
@@ -236,8 +248,9 @@
|
| 6138 |
.models-sidebar-badge {
|
| 6139 |
flex-shrink: 0;
|
| 6140 |
color: var(--text-dim);
|
|
|
|
| 6145 |
}
|
| 6146 |
|
| 6147 |
.oauth-paste-panel {
|
| 6148 |
+
@@ -277,9 +290,8 @@
|
| 6149 |
/* An input's intrinsic width would otherwise push the button out of a narrow pane. */
|
| 6150 |
min-width: 0;
|
| 6151 |
padding: 6px 9px;
|
|
|
|
| 6157 |
color: var(--text);
|
| 6158 |
font-family: var(--font-mono);
|
| 6159 |
font-size: 12px;
|
| 6160 |
+
@@ -290,9 +302,9 @@
|
| 6161 |
flex-shrink: 0;
|
| 6162 |
padding: 6px 12px;
|
| 6163 |
border: none;
|
|
|
|
| 6170 |
cursor: pointer;
|
| 6171 |
font-size: 12px;
|
| 6172 |
font-weight: 600;
|
| 6173 |
+
@@ -304,13 +316,13 @@
|
| 6174 |
cursor: not-allowed;
|
| 6175 |
}
|
| 6176 |
|
|
|
|
| 6187 |
}
|
| 6188 |
|
| 6189 |
.enabled-models-banner-text {
|
| 6190 |
+
@@ -370,8 +382,9 @@
|
| 6191 |
.enabled-models-count {
|
| 6192 |
margin-right: auto;
|
| 6193 |
color: var(--text-dim);
|
|
|
|
| 6198 |
}
|
| 6199 |
|
| 6200 |
.enabled-models-note {
|
| 6201 |
+
@@ -383,7 +396,7 @@
|
| 6202 |
.enabled-models-error {
|
| 6203 |
padding: 7px 9px;
|
| 6204 |
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
|
|
| 6207 |
color: #ef4444;
|
| 6208 |
font-size: 11px;
|
| 6209 |
line-height: 1.4;
|
| 6210 |
+
@@ -401,10 +414,9 @@
|
| 6211 |
box-sizing: border-box;
|
| 6212 |
height: 30px;
|
| 6213 |
padding: 0 9px;
|
|
|
|
| 6220 |
color: var(--text);
|
| 6221 |
font-family: inherit;
|
| 6222 |
font-size: 12px;
|
| 6223 |
+
@@ -413,8 +425,8 @@
|
| 6224 |
.enabled-models-list {
|
| 6225 |
max-height: 360px;
|
| 6226 |
border: 1px solid var(--border);
|
|
|
|
| 6231 |
overflow-y: auto;
|
| 6232 |
}
|
| 6233 |
|
| 6234 |
+
@@ -457,9 +469,9 @@
|
| 6235 |
.enabled-models-pin {
|
| 6236 |
flex-shrink: 0;
|
| 6237 |
padding: 1px 4px;
|
|
|
|
| 6244 |
font-size: 9px;
|
| 6245 |
}
|
| 6246 |
|
| 6247 |
+
@@ -496,10 +508,9 @@
|
| 6248 |
align-items: center;
|
| 6249 |
justify-content: space-between;
|
| 6250 |
gap: 18px;
|
|
|
|
| 6258 |
}
|
| 6259 |
|
| 6260 |
.agents-feature-copy {
|
| 6261 |
+
@@ -541,7 +552,7 @@
|
| 6262 |
height: 28px;
|
| 6263 |
padding: 0 6px;
|
| 6264 |
border: 1px solid var(--border);
|
|
|
|
| 6267 |
background: var(--bg);
|
| 6268 |
color: var(--text);
|
| 6269 |
font: inherit;
|
| 6270 |
+
@@ -566,7 +577,7 @@
|
| 6271 |
flex: 1;
|
| 6272 |
min-width: 0;
|
| 6273 |
min-height: 0;
|
|
|
|
| 6276 |
overflow-y: auto;
|
| 6277 |
font-family: inherit;
|
| 6278 |
font-size: 12px;
|
| 6279 |
+
@@ -628,13 +639,12 @@
|
| 6280 |
line-height: 1.35;
|
| 6281 |
}
|
| 6282 |
|
|
|
|
| 6288 |
+ font-size: 12px;
|
| 6289 |
+ font-weight: 400;
|
| 6290 |
line-height: 1.35;
|
| 6291 |
+
- letter-spacing: 0;
|
| 6292 |
- text-transform: uppercase;
|
| 6293 |
}
|
| 6294 |
|
| 6295 |
.config-field {
|
| 6296 |
+
@@ -662,7 +672,7 @@
|
| 6297 |
background: var(--border);
|
| 6298 |
border: 5px solid transparent;
|
| 6299 |
background-clip: padding-box;
|
|
|
|
| 6302 |
}
|
| 6303 |
|
| 6304 |
.config-field-label {
|
| 6305 |
+
@@ -697,15 +707,15 @@
|
| 6306 |
.config-scope-tag {
|
| 6307 |
flex-shrink: 0;
|
| 6308 |
padding: 1px 5px;
|
|
|
|
| 6321 |
}
|
| 6322 |
|
| 6323 |
.config-scope-switch-field {
|
| 6324 |
+
@@ -730,7 +740,7 @@
|
| 6325 |
height: 30px;
|
| 6326 |
overflow: hidden;
|
| 6327 |
border: 1px solid var(--border);
|
|
|
|
| 6330 |
}
|
| 6331 |
|
| 6332 |
.config-scope-switch-option {
|
| 6333 |
+
@@ -760,7 +770,7 @@
|
| 6334 |
.config-scope-switch.is-small {
|
| 6335 |
display: flex;
|
| 6336 |
height: auto;
|
|
|
|
| 6339 |
font-size: 12px;
|
| 6340 |
}
|
| 6341 |
|
| 6342 |
+
@@ -891,9 +901,8 @@
|
| 6343 |
width: 100%;
|
| 6344 |
height: 36px;
|
| 6345 |
padding: 0 11px;
|
|
|
|
| 6351 |
color: var(--text);
|
| 6352 |
font-family: var(--font-mono);
|
| 6353 |
font-size: 12px;
|
| 6354 |
+
@@ -935,8 +944,8 @@
|
| 6355 |
min-height: 30px;
|
| 6356 |
padding: 6px 9px;
|
| 6357 |
border: 1px solid var(--border);
|
|
|
|
| 6362 |
color: var(--text-dim);
|
| 6363 |
cursor: pointer;
|
| 6364 |
font-family: var(--font-mono);
|
| 6365 |
+
@@ -975,12 +984,15 @@
|
| 6366 |
white-space: pre-wrap;
|
| 6367 |
}
|
| 6368 |
|
|
|
|
| 6381 |
}
|
| 6382 |
|
| 6383 |
.config-notice.has-action {
|
| 6384 |
+
@@ -1104,8 +1116,13 @@
|
| 6385 |
align-items: center;
|
| 6386 |
gap: 8px;
|
| 6387 |
min-height: 52px;
|
|
|
|
| 6397 |
}
|
| 6398 |
|
| 6399 |
.config-footer-status {
|
| 6400 |
+
@@ -1159,44 +1176,47 @@
|
| 6401 |
gap: 6px;
|
| 6402 |
border-width: 1px;
|
| 6403 |
border-style: solid;
|
|
|
|
| 6405 |
+ border-radius: var(--radius-sm);
|
| 6406 |
cursor: pointer;
|
| 6407 |
font-family: inherit;
|
| 6408 |
+
- letter-spacing: 0;
|
| 6409 |
+
line-height: 1;
|
| 6410 |
+
white-space: nowrap;
|
| 6411 |
transition: background-color 0.14s ease, border-color 0.14s ease, color 0.14s ease, box-shadow 0.14s ease;
|
| 6412 |
}
|
| 6413 |
|
|
|
|
| 6463 |
}
|
| 6464 |
|
| 6465 |
.config-button-ghost {
|
| 6466 |
+
@@ -1206,24 +1226,31 @@
|
| 6467 |
}
|
| 6468 |
|
| 6469 |
.config-button-primary:not(:disabled):hover {
|
|
|
|
| 6503 |
outline-offset: 2px;
|
| 6504 |
}
|
| 6505 |
|
| 6506 |
+
@@ -1248,33 +1275,41 @@
|
| 6507 |
margin-left: auto;
|
| 6508 |
}
|
| 6509 |
|
|
|
|
| 6557 |
.config-switch::before {
|
| 6558 |
content: "";
|
| 6559 |
position: absolute;
|
| 6560 |
+
@@ -1283,7 +1318,7 @@
|
| 6561 |
|
| 6562 |
.config-switch:disabled {
|
| 6563 |
cursor: default;
|
|
|
|
| 6566 |
}
|
| 6567 |
|
| 6568 |
.config-switch.is-loading:disabled {
|
| 6569 |
+
@@ -1294,42 +1329,38 @@
|
| 6570 |
position: absolute;
|
| 6571 |
top: 2px;
|
| 6572 |
left: 2px;
|
|
|
|
| 6623 |
}
|
| 6624 |
|
| 6625 |
.config-list-action-button {
|
| 6626 |
+
@@ -1342,7 +1373,7 @@
|
| 6627 |
min-height: 30px;
|
| 6628 |
padding: 0 8px;
|
| 6629 |
border: 0;
|
|
|
|
| 6632 |
background: transparent;
|
| 6633 |
color: var(--text-dim);
|
| 6634 |
cursor: pointer;
|
| 6635 |
+
@@ -1358,7 +1389,7 @@
|
| 6636 |
|
| 6637 |
.config-list-action-button[aria-current="page"] {
|
| 6638 |
background: var(--bg-selected);
|
|
|
|
| 6641 |
}
|
| 6642 |
|
| 6643 |
.config-list-action-button:disabled {
|
| 6644 |
+
@@ -1382,6 +1413,14 @@
|
| 6645 |
opacity: 0.7;
|
| 6646 |
}
|
| 6647 |
|
|
|
|
| 6656 |
.settings-dialog-backdrop {
|
| 6657 |
position: fixed;
|
| 6658 |
inset: 0;
|
| 6659 |
+
@@ -1389,7 +1428,7 @@
|
| 6660 |
display: flex;
|
| 6661 |
align-items: center;
|
| 6662 |
justify-content: center;
|
|
|
|
| 6665 |
}
|
| 6666 |
|
| 6667 |
/* Focus moves to the dialog itself when Settings opens (tabIndex -1); it is not a control, so no ring. */
|
| 6668 |
+
@@ -1398,127 +1437,184 @@
|
| 6669 |
}
|
| 6670 |
|
| 6671 |
.settings-dialog-surface {
|
|
|
|
| 6921 |
}
|
| 6922 |
|
| 6923 |
.settings-dialog-main {
|
| 6924 |
+
@@ -1537,136 +1633,197 @@
|
| 6925 |
min-height: 0;
|
| 6926 |
}
|
| 6927 |
|
|
|
|
| 7198 |
font-family: var(--font-mono);
|
| 7199 |
font-weight: var(--font-mono-weight);
|
| 7200 |
}
|
| 7201 |
+
@@ -1676,197 +1833,324 @@
|
| 7202 |
}
|
| 7203 |
|
| 7204 |
.settings-font-input:focus-visible {
|
|
|
|
| 7654 |
}
|
| 7655 |
|
| 7656 |
@media (max-width: 640px) {
|
| 7657 |
+
@@ -1887,13 +2171,13 @@
|
| 7658 |
width: 100%;
|
| 7659 |
height: 190px;
|
| 7660 |
border-right: 0;
|
|
|
|
| 7670 |
}
|
| 7671 |
|
| 7672 |
.agents-feature-actions {
|
| 7673 |
+
@@ -1905,29 +2189,6 @@
|
| 7674 |
padding: 14px;
|
| 7675 |
}
|
| 7676 |
|
|
|
|
| 7700 |
}
|
| 7701 |
|
| 7702 |
@keyframes saved-pop {
|
| 7703 |
+
@@ -2066,7 +2327,7 @@
|
| 7704 |
max-width: 100%;
|
| 7705 |
padding: 0 5px;
|
| 7706 |
border: 1px solid var(--border);
|
|
|
|
| 7709 |
background: var(--bg-panel);
|
| 7710 |
color: var(--text);
|
| 7711 |
font-family: var(--font-mono);
|
| 7712 |
+
@@ -2085,7 +2346,7 @@
|
| 7713 |
padding: 6px 8px;
|
| 7714 |
overflow: auto;
|
| 7715 |
border: 1px solid var(--border);
|
|
|
|
| 7718 |
background: var(--bg-panel);
|
| 7719 |
color: var(--text-muted);
|
| 7720 |
font-family: var(--font-mono);
|
| 7721 |
+
@@ -2172,9 +2433,8 @@
|
| 7722 |
min-width: 0;
|
| 7723 |
height: 32px;
|
| 7724 |
padding: 0 10px;
|
|
|
|
| 7730 |
color: var(--text);
|
| 7731 |
font-family: var(--font-mono);
|
| 7732 |
font-size: 12px;
|
| 7733 |
+
@@ -2266,10 +2526,9 @@ select.mcp-add-input {
|
| 7734 |
max-width: 100%;
|
| 7735 |
max-height: 100%;
|
| 7736 |
overflow: hidden;
|
|
|
|
| 7744 |
}
|
| 7745 |
|
| 7746 |
/* Focus moves to the dialog itself when it opens (tabIndex -1); it is not a control, so no ring. */
|
| 7747 |
+
@@ -2282,7 +2541,7 @@ select.mcp-add-input {
|
| 7748 |
flex: 1 1 auto;
|
| 7749 |
gap: 12px;
|
| 7750 |
min-height: 0;
|
|
|
|
| 7753 |
overflow: auto;
|
| 7754 |
overscroll-behavior: contain;
|
| 7755 |
}
|
| 7756 |
+
@@ -2315,7 +2574,7 @@ select.mcp-add-input {
|
| 7757 |
margin-top: 10px;
|
| 7758 |
padding: 8px 10px;
|
| 7759 |
border: 1px solid var(--border);
|
|
|
|
| 7762 |
background: var(--bg);
|
| 7763 |
color: var(--text);
|
| 7764 |
font-family: var(--font-mono);
|
| 7765 |
+
@@ -2333,7 +2592,7 @@ select.mcp-add-input {
|
| 7766 |
.project-trust-error {
|
| 7767 |
flex-shrink: 0;
|
| 7768 |
max-height: 30vh;
|
|
|
|
| 7771 |
overflow: auto;
|
| 7772 |
color: #ef4444;
|
| 7773 |
font-size: 12px;
|
| 7774 |
+
@@ -2353,19 +2612,20 @@ select.mcp-add-input {
|
| 7775 |
flex-shrink: 0;
|
| 7776 |
justify-content: flex-end;
|
| 7777 |
gap: 8px;
|
|
|
|
| 7799 |
}
|
| 7800 |
|
| 7801 |
.project-trust-button:disabled {
|
| 7802 |
+
@@ -2373,10 +2633,8 @@ select.mcp-add-input {
|
| 7803 |
}
|
| 7804 |
|
| 7805 |
.project-trust-button.is-primary {
|
|
|
|
| 7812 |
}
|
| 7813 |
|
| 7814 |
.project-trust-button.is-primary:disabled {
|
| 7815 |
+
@@ -2450,7 +2708,7 @@ select.mcp-add-input {
|
| 7816 |
min-width: 0;
|
| 7817 |
padding: 8px 10px;
|
| 7818 |
border: 1px solid var(--border);
|
|
|
|
| 7821 |
background: var(--bg);
|
| 7822 |
}
|
| 7823 |
|
| 7824 |
+
@@ -2472,7 +2730,7 @@ select.mcp-add-input {
|
| 7825 |
.project-trust-mcp-tag {
|
| 7826 |
flex-shrink: 0;
|
| 7827 |
padding: 1px 5px;
|
|
|
|
| 7830 |
background: rgba(120, 120, 120, 0.12);
|
| 7831 |
color: var(--text-dim);
|
| 7832 |
font-size: 10px;
|
| 7833 |
+
@@ -2493,7 +2751,7 @@ select.mcp-add-input {
|
| 7834 |
display: inline-block;
|
| 7835 |
margin: 2px 4px 0 0;
|
| 7836 |
padding: 0 4px;
|
|
|
|
| 9317 |
|
| 9318 |
.sidebar-sheet .sidebar-menu-question-text {
|
| 9319 |
diff --git a/app/sidebar.css b/app/sidebar.css
|
| 9320 |
+
index 86728a2..acb525e 100644
|
| 9321 |
--- a/app/sidebar.css
|
| 9322 |
+++ b/app/sidebar.css
|
| 9323 |
@@ -91,8 +91,9 @@
|
|
|
|
| 9429 |
border-radius: 50%;
|
| 9430 |
opacity: 0.35;
|
| 9431 |
animation: session-tree-status-pulse 1.6s ease-in-out infinite;
|
| 9432 |
+
@@ -297,20 +278,22 @@ a.session-tree-main {
|
| 9433 |
}
|
| 9434 |
}
|
| 9435 |
|
|
|
|
| 9446 |
- font-size: 12px;
|
| 9447 |
- text-overflow: ellipsis;
|
| 9448 |
+ font-size: 13px;
|
|
|
|
| 9449 |
white-space: nowrap;
|
| 9450 |
+ -webkit-mask-image: linear-gradient(to right, black calc(100% - 20px), transparent);
|
| 9451 |
+ mask-image: linear-gradient(to right, black calc(100% - 20px), transparent);
|
|
|
|
| 9457 |
}
|
| 9458 |
|
| 9459 |
.session-tree-session.is-archived .session-tree-title {
|
| 9460 |
+
@@ -321,6 +304,8 @@ a.session-tree-main {
|
| 9461 |
from its source ("… · 3f9a"), never the suffix. */
|
| 9462 |
.session-tree-title.has-fork-suffix {
|
| 9463 |
display: flex;
|
|
|
|
| 9466 |
}
|
| 9467 |
|
| 9468 |
.session-tree-title-base {
|
| 9469 |
+
@@ -384,16 +369,23 @@ a.session-tree-main {
|
| 9470 |
align-items: center;
|
| 9471 |
}
|
| 9472 |
|
|
|
|
| 9494 |
color: var(--text-dim);
|
| 9495 |
}
|
| 9496 |
|
| 9497 |
+
@@ -408,11 +400,18 @@ a.session-tree-main {
|
| 9498 |
background: var(--bg-hover);
|
| 9499 |
}
|
| 9500 |
|
|
|
|
| 9516 |
}
|
| 9517 |
|
| 9518 |
.session-tree-meta.is-running {
|
| 9519 |
+
@@ -436,7 +435,10 @@ a.session-tree-main {
|
| 9520 |
* :focus-within fallback in the @supports block after the group rules.
|
| 9521 |
*/
|
| 9522 |
.session-tree-session:has(:focus-visible) .session-tree-action {
|
|
|
|
| 9528 |
}
|
| 9529 |
|
| 9530 |
.session-tree-session:has(:focus-visible) .session-tree-meta {
|
| 9531 |
+
@@ -454,7 +456,7 @@ a.session-tree-main {
|
| 9532 |
padding: 0 8px;
|
| 9533 |
box-sizing: border-box;
|
| 9534 |
border: 1px solid var(--accent);
|
|
|
|
| 9537 |
outline: none;
|
| 9538 |
background: var(--bg);
|
| 9539 |
color: var(--text);
|
| 9540 |
+
@@ -483,7 +485,7 @@ a.session-tree-main {
|
| 9541 |
gap: 4px;
|
| 9542 |
height: 24px;
|
| 9543 |
padding: 0 10px;
|
|
|
|
| 9546 |
font-size: 12px;
|
| 9547 |
white-space: nowrap;
|
| 9548 |
}
|
| 9549 |
+
@@ -518,8 +520,8 @@ a.session-tree-main {
|
| 9550 |
right: var(--session-tree-inset-right);
|
| 9551 |
left: var(--session-tree-inset-left);
|
| 9552 |
gap: 2px;
|
|
|
|
| 9557 |
-webkit-user-select: none;
|
| 9558 |
user-select: none;
|
| 9559 |
-webkit-touch-callout: none;
|
| 9560 |
+
@@ -548,7 +550,7 @@ a.session-tree-main {
|
| 9561 |
position: absolute;
|
| 9562 |
right: var(--session-tree-inset-right);
|
| 9563 |
left: var(--session-tree-inset-left);
|
|
|
|
| 9566 |
background: color-mix(in srgb, var(--bg-panel) 55%, transparent);
|
| 9567 |
pointer-events: none;
|
| 9568 |
}
|
| 9569 |
+
@@ -589,7 +591,7 @@ a.session-tree-main {
|
| 9570 |
height: 22px;
|
| 9571 |
padding: 0 10px;
|
| 9572 |
border: 1px solid var(--border);
|
|
|
|
| 9575 |
background: var(--bg-panel);
|
| 9576 |
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
|
| 9577 |
color: var(--text);
|
| 9578 |
+
@@ -618,7 +620,7 @@ a.session-tree-main {
|
| 9579 |
padding: 0 0 0 5px;
|
| 9580 |
color: var(--text-muted);
|
| 9581 |
font-size: 12px;
|
|
|
|
| 9584 |
text-align: left;
|
| 9585 |
white-space: nowrap;
|
| 9586 |
}
|
| 9587 |
+
@@ -669,19 +671,27 @@ a.session-tree-main {
|
| 9588 |
width: 6px;
|
| 9589 |
height: 6px;
|
| 9590 |
border-radius: 50%;
|
|
|
|
| 9615 |
}
|
| 9616 |
|
| 9617 |
.session-tree-group:hover .session-tree-summary,
|
| 9618 |
+
@@ -690,7 +700,10 @@ a.session-tree-main {
|
| 9619 |
}
|
| 9620 |
|
| 9621 |
.session-tree-group:has(:focus-visible) .session-tree-group-actions {
|
|
|
|
| 9627 |
}
|
| 9628 |
|
| 9629 |
.session-tree-group:has(:focus-visible) .session-tree-summary {
|
| 9630 |
+
@@ -698,9 +711,18 @@ a.session-tree-main {
|
| 9631 |
}
|
| 9632 |
|
| 9633 |
@supports not selector(:has(*)) {
|
|
|
|
| 9648 |
}
|
| 9649 |
|
| 9650 |
.session-tree-session:focus-within .session-tree-meta,
|
| 9651 |
+
@@ -726,7 +748,7 @@ a.session-tree-main {
|
| 9652 |
width: 22px;
|
| 9653 |
height: 22px;
|
| 9654 |
padding: 0;
|
|
|
|
| 9657 |
color: var(--text-dim);
|
| 9658 |
}
|
| 9659 |
|
| 9660 |
+
@@ -787,7 +809,7 @@ a.session-tree-main {
|
| 9661 |
gap: 3px;
|
| 9662 |
min-width: 0;
|
| 9663 |
padding: 0 4px 0 5px;
|
|
|
|
| 9666 |
color: var(--text-dim);
|
| 9667 |
font-size: 12px;
|
| 9668 |
text-align: left;
|
| 9669 |
+
@@ -838,16 +860,21 @@ a.session-tree-main {
|
| 9670 |
|
| 9671 |
/* Phone layout: 44px rows, a step larger type for titles and project names,
|
| 9672 |
⋯ and the group buttons always shown, no archive quick button. */
|
|
|
|
| 9690 |
}
|
| 9691 |
|
| 9692 |
.session-tree.is-mobile .session-tree-session:hover .session-tree-meta {
|
| 9693 |
+
@@ -859,7 +886,10 @@ a.session-tree-main {
|
| 9694 |
}
|
| 9695 |
|
| 9696 |
.session-tree.is-mobile .session-tree-group-actions {
|
|
|
|
| 9702 |
}
|
| 9703 |
|
| 9704 |
.session-tree.is-mobile .session-tree-group .session-tree-summary {
|
| 9705 |
+
@@ -884,7 +914,10 @@ a.session-tree-main {
|
| 9706 |
/* Touch screens wider than a phone have no hover: keep ⋯ reachable. */
|
| 9707 |
@media (hover: none) {
|
| 9708 |
.session-tree-more-action {
|
|
|
|
| 9714 |
}
|
| 9715 |
}
|
| 9716 |
|
| 9717 |
+
@@ -942,9 +975,10 @@ a.session-tree-main {
|
| 9718 |
display: flex;
|
| 9719 |
flex: none;
|
| 9720 |
align-items: stretch;
|
|
|
|
| 9726 |
}
|
| 9727 |
|
| 9728 |
/* The tablist itself holds only the two tabs (ARIA allows nothing else in it). */
|
| 9729 |
+
@@ -954,44 +988,30 @@ a.session-tree-main {
|
| 9730 |
align-items: stretch;
|
| 9731 |
}
|
| 9732 |
|
|
|
|
| 9778 |
.sidebar-tab-icon {
|
| 9779 |
flex: none;
|
| 9780 |
color: var(--text-dim);
|
| 9781 |
+
@@ -1006,17 +1026,12 @@ a.session-tree-main {
|
| 9782 |
|
| 9783 |
.sidebar-tab.is-selected,
|
| 9784 |
.sidebar-search-toggle.is-active {
|
|
|
|
| 9798 |
}
|
| 9799 |
|
| 9800 |
.sidebar-tab-count {
|
| 9801 |
+
@@ -1036,27 +1051,36 @@ a.session-tree-main {
|
| 9802 |
beside the two cells at the sidebar's 180px minimum, and at 11px so do
|
| 9803 |
the versions with a digit to spare). */
|
| 9804 |
.sidebar-brand {
|
|
|
|
| 9821 |
- font-family: var(--font-mono);
|
| 9822 |
- font-size: 15px;
|
| 9823 |
- font-weight: 700;
|
| 9824 |
+
- letter-spacing: -0.01em;
|
| 9825 |
+
- line-height: normal;
|
| 9826 |
+ color: var(--text-strong);
|
| 9827 |
+ font-family: var(--font-ui);
|
| 9828 |
+ font-size: 13px;
|
| 9829 |
+ font-weight: 600;
|
| 9830 |
+ font-variant-numeric: tabular-nums;
|
|
|
|
|
|
|
| 9831 |
+ line-height: 1;
|
| 9832 |
text-align: left;
|
| 9833 |
white-space: nowrap;
|
|
|
|
| 9846 |
}
|
| 9847 |
|
| 9848 |
.sidebar-brand:focus {
|
| 9849 |
+
@@ -1064,8 +1088,10 @@ a.session-tree-main {
|
| 9850 |
}
|
| 9851 |
|
| 9852 |
.sidebar-brand:focus-visible {
|
|
|
|
| 9859 |
}
|
| 9860 |
|
| 9861 |
.sidebar-header-spacer {
|
| 9862 |
+
@@ -1078,7 +1104,7 @@ a.session-tree-main {
|
| 9863 |
}
|
| 9864 |
|
| 9865 |
.sidebar-search-toggle {
|
|
|
|
| 9868 |
padding: 0;
|
| 9869 |
}
|
| 9870 |
|
| 9871 |
+
@@ -1093,7 +1119,7 @@ a.session-tree-main {
|
| 9872 |
label is still each one's name. */
|
| 9873 |
.sidebar-header[data-fit="1"] .sidebar-new-button,
|
| 9874 |
.sidebar-header[data-fit="2"] .sidebar-new-button {
|
|
|
|
| 9877 |
padding: 0;
|
| 9878 |
}
|
| 9879 |
|
| 9880 |
+
@@ -1105,6 +1131,11 @@ a.session-tree-main {
|
| 9881 |
font-size: 11px;
|
| 9882 |
}
|
| 9883 |
|
|
|
|
| 9889 |
.sidebar-header[data-fit="1"] .sidebar-new-label,
|
| 9890 |
.sidebar-header[data-fit="2"] .sidebar-new-label,
|
| 9891 |
.sidebar-header[data-fit="2"] .sidebar-tab-label {
|
| 9892 |
+
@@ -1122,8 +1153,9 @@ a.session-tree-main {
|
| 9893 |
.sidebar-archive-back:focus-visible,
|
| 9894 |
.sidebar-tool-button:focus-visible,
|
| 9895 |
.sidebar-files-section-toggle:focus-visible {
|
|
|
|
| 9901 |
}
|
| 9902 |
|
| 9903 |
.sidebar-panel {
|
| 9904 |
+
@@ -1202,7 +1234,7 @@ a.session-tree-main {
|
| 9905 |
the root too), and its time 4px inside the row. The header row's keys and
|
| 9906 |
the picker's boxes end there. */
|
| 9907 |
.session-sidebar.is-files-below {
|
|
|
|
| 9910 |
}
|
| 9911 |
|
| 9912 |
/* The files section's header row (files below the sessions only), one row
|
| 9913 |
+
@@ -1246,7 +1278,7 @@ a.session-tree-main {
|
| 9914 |
padding: 0 5px;
|
| 9915 |
overflow: hidden;
|
| 9916 |
border: 0;
|
|
|
|
| 9919 |
background: transparent;
|
| 9920 |
color: var(--text-muted);
|
| 9921 |
font: inherit;
|
| 9922 |
+
@@ -1272,10 +1304,8 @@ a.session-tree-main {
|
| 9923 |
.sidebar-files-section-label {
|
| 9924 |
min-width: 0;
|
| 9925 |
overflow: hidden;
|
|
|
|
| 9931 |
}
|
| 9932 |
|
| 9933 |
/* The changed files' count, as the pinned section's (.session-tree-pinned-count). */
|
| 9934 |
+
@@ -1294,7 +1324,7 @@ a.session-tree-main {
|
| 9935 |
display: flex;
|
| 9936 |
flex: 1000 0 calc(7 * 18px);
|
| 9937 |
align-items: center;
|
|
|
|
| 9940 |
}
|
| 9941 |
|
| 9942 |
/* The section's keys: the files tab's square keys, a step under the row's
|
| 9943 |
+
@@ -1302,9 +1332,9 @@ a.session-tree-main {
|
| 9944 |
.sidebar-files-keys .sidebar-tool-button {
|
| 9945 |
flex: 1 1 0;
|
| 9946 |
min-width: 18px;
|
|
|
|
| 9953 |
}
|
| 9954 |
|
| 9955 |
/* Under that row (or the whole files tab): the head, then the tree filling
|
| 9956 |
+
@@ -1317,31 +1347,7 @@ a.session-tree-main {
|
| 9957 |
min-height: 0;
|
| 9958 |
}
|
| 9959 |
|
|
|
|
| 9986 |
slides in over the tree, which stays mounted under it (hidden once the
|
| 9987 |
archive is in), and away again. SessionTree fills each. */
|
| 9988 |
.sidebar-sessions-views {
|
| 9989 |
+
@@ -1413,7 +1419,7 @@ a.session-tree-main {
|
| 9990 |
height: 26px;
|
| 9991 |
padding: 0 6px;
|
| 9992 |
border: 0;
|
|
|
|
| 9995 |
background: transparent;
|
| 9996 |
color: var(--text-muted);
|
| 9997 |
font-size: 12px;
|
| 9998 |
+
@@ -1479,10 +1485,11 @@ a.session-tree-main {
|
| 9999 |
six (the changes view is disabled, not gone, without changes), so nothing
|
| 10000 |
moves as the count flips. At the sidebar's 180px minimum they give way
|
| 10001 |
together, down to 21px wide, and the gap before the views closes. */
|
|
|
|
| 10008 |
margin-top: 4px;
|
| 10009 |
}
|
| 10010 |
|
| 10011 |
+
@@ -1492,17 +1499,18 @@ a.session-tree-main {
|
| 10012 |
margin-left: auto;
|
| 10013 |
}
|
| 10014 |
|
|
|
|
| 10032 |
background: transparent;
|
| 10033 |
color: var(--text-dim);
|
| 10034 |
cursor: pointer;
|
| 10035 |
+
@@ -1510,8 +1518,8 @@ a.session-tree-main {
|
| 10036 |
}
|
| 10037 |
|
| 10038 |
.sidebar-tool-button:not(:disabled):hover {
|
|
|
|
| 10045 |
.sidebar-tool-button.is-active,
|
| 10046 |
diff --git a/app/ui.css b/app/ui.css
|
| 10047 |
new file mode 100644
|
| 10048 |
+
index 0000000..ad43ed3
|
| 10049 |
--- /dev/null
|
| 10050 |
+++ b/app/ui.css
|
| 10051 |
+
@@ -0,0 +1,760 @@
|
| 10052 |
+/* The shared components on Base UI (components/ui/*): menus, tooltips,
|
| 10053 |
+ popovers and the composer's suggestion list. Popups are portaled to <body>,
|
| 10054 |
+ so they stack by the positioner's z-index, above every pane. */
|
|
|
|
| 10319 |
+ background: color-mix(in srgb, var(--primary-contrast) 16%, transparent);
|
| 10320 |
+ font-family: inherit;
|
| 10321 |
+ font-size: 11px;
|
|
|
|
| 10322 |
+}
|
| 10323 |
+
|
| 10324 |
+/* ---------------------------------------------------------------- popovers */
|
|
|
|
| 20897 |
</div>
|
| 20898 |
);
|
| 20899 |
});
|
| 20900 |
+
diff --git a/components/MessageView.copy.test.mjs b/components/MessageView.copy.test.mjs
|
| 20901 |
+
new file mode 100644
|
| 20902 |
+
index 0000000..f4d9392
|
| 20903 |
+
--- /dev/null
|
| 20904 |
+
+++ b/components/MessageView.copy.test.mjs
|
| 20905 |
+
@@ -0,0 +1,26 @@
|
| 20906 |
+
+import assert from "node:assert/strict";
|
| 20907 |
+
+import { readFileSync } from "node:fs";
|
| 20908 |
+
+import test from "node:test";
|
| 20909 |
+
+
|
| 20910 |
+
+const view = readFileSync(new URL("./MessageView.tsx", import.meta.url), "utf8");
|
| 20911 |
+
+const css = readFileSync(new URL("../app/globals.css", import.meta.url), "utf8");
|
| 20912 |
+
+
|
| 20913 |
+
+test("only an answer has a Copy, shown on hover like its row; whitespace text blocks draw nothing", () => {
|
| 20914 |
+
+ // DeepSeek sends a "\n\n" text block beside each tool call: it is no text, so no block and no Copy of it.
|
| 20915 |
+
+ assert.match(view, /!\(block\.type === "text" && !\(block as TextContent\)\.text\.trim\(\)\)/);
|
| 20916 |
+
+ assert.match(view, /\.join\("\\n"\)\n\s*\.trim\(\);/);
|
| 20917 |
+
+ // An answer is text without tool calls; a running turn's other messages are steps (no actions row).
|
| 20918 |
+
+ assert.match(view, /const isAnswer = Boolean\(textContent\) && !blocks\.some\(\(b\) => b\.type === "toolCall"\);/);
|
| 20919 |
+
+ assert.match(view, /\{!asStep && !isStreaming && isAnswer && \(/);
|
| 20920 |
+
+ // Copy sits inside the row's hover reveal, with the model, usage and time.
|
| 20921 |
+
+ const row = view.slice(view.indexOf("{!asStep && !isStreaming && isAnswer && ("));
|
| 20922 |
+
+ assert.ok(row.indexOf('<div className="message-actions-reveal">') < row.indexOf("onClick={copyContent}"));
|
| 20923 |
+
+ assert.match(css, /@media \(hover: none\) \{ \.message-actions-reveal \{ opacity: 1; \} \}/);
|
| 20924 |
+
+});
|
| 20925 |
+
+
|
| 20926 |
+
+test("steps keep one 4px rhythm, folded or live, and the answer sits 8px under them", () => {
|
| 20927 |
+
+ assert.match(css, /\.turn-reply\.is-step \.reply-blocks \{ gap: 4px; \}/);
|
| 20928 |
+
+ assert.match(css, /:has\(> \.turn-reply\.is-step\)\) \+ :is\(\.turn-reply\.is-step, :has\(> \.turn-reply\.is-step\)\) \{ margin-top: 4px; \}/);
|
| 20929 |
+
+ assert.match(css, /\{ margin-top: 8px; \}/);
|
| 20930 |
+
+ assert.match(css, /\.steps-list \{[^}]*gap: 0;[^}]*margin: 4px 0 0 1px;/);
|
| 20931 |
+
+});
|
| 20932 |
diff --git a/components/MessageView.test.mjs b/components/MessageView.test.mjs
|
| 20933 |
index 515b033..12d546b 100644
|
| 20934 |
--- a/components/MessageView.test.mjs
|
|
|
|
| 20968 |
|
| 20969 |
test("collapses a displayed custom message from its header alone", () => {
|
| 20970 |
diff --git a/components/MessageView.tsx b/components/MessageView.tsx
|
| 20971 |
+
index f2ff1de..0dfb320 100644
|
| 20972 |
--- a/components/MessageView.tsx
|
| 20973 |
+++ b/components/MessageView.tsx
|
| 20974 |
@@ -5,13 +5,16 @@ import ReactMarkdown from "react-markdown";
|
|
|
|
| 21430 |
}: {
|
| 21431 |
message: AssistantMessage;
|
| 21432 |
isStreaming?: boolean;
|
| 21433 |
+
@@ -669,17 +589,21 @@ function AssistantMessageView({
|
| 21434 |
onCompact?: () => void;
|
| 21435 |
isCompacting?: boolean;
|
| 21436 |
compactError?: string | null;
|
|
|
|
| 21439 |
}) {
|
| 21440 |
const { t } = useI18n();
|
| 21441 |
const time = showTimestamp ? formatTime(message.timestamp) : null;
|
| 21442 |
+
const blockItems = useMemo(() => (message.content ?? [])
|
| 21443 |
+
.map((block, originalIndex) => ({ block, originalIndex }))
|
| 21444 |
+
- .filter(({ block }) => !isEmptyThinkingBlock(block, { isStreaming })), [message.content, isStreaming]);
|
| 21445 |
+
+ // A text block of whitespace alone (DeepSeek sends "\n\n" beside each tool call) draws
|
| 21446 |
+
+ // nothing: rendered, it was an empty element the blocks' gap counted twice.
|
| 21447 |
+
+ .filter(({ block }) => !isEmptyThinkingBlock(block, { isStreaming })
|
| 21448 |
+
+ && !(block.type === "text" && !(block as TextContent).text.trim())), [message.content, isStreaming]);
|
| 21449 |
+
const blocks = useMemo(() => blockItems.map(({ block }) => block), [blockItems]);
|
| 21450 |
const providerError = getAssistantErrorMessage(message, { isStreaming });
|
| 21451 |
const truncated = isAssistantTruncated(message, { isStreaming });
|
| 21452 |
const unansweredTruncation = truncated && !hasAssistantAnswer(message);
|
|
|
|
| 21454 |
const [copied, setCopied] = useState(false);
|
| 21455 |
const streamStartRef = useRef<StreamRateStart | null>(null);
|
| 21456 |
const rateKey = streamRateKey(message);
|
| 21457 |
+
@@ -741,7 +665,13 @@ function AssistantMessageView({
|
| 21458 |
+
const textContent = blocks
|
| 21459 |
+
.filter((b): b is TextContent => b.type === "text")
|
| 21460 |
+
.map((b) => b.text)
|
| 21461 |
+
- .join("\n");
|
| 21462 |
+
+ .join("\n")
|
| 21463 |
+
+ .trim();
|
| 21464 |
+
+ // An answer is text without tool calls. Anything else is a step of its turn, shown as one
|
| 21465 |
+
+ // (no actions row, the steps' rhythm) also while the turn runs and its steps are not yet
|
| 21466 |
+
+ // folded under their summary: each live step used to carry a Copy of its "\n\n".
|
| 21467 |
+
+ const isAnswer = Boolean(textContent) && !blocks.some((b) => b.type === "toolCall");
|
| 21468 |
+
+ const asStep = inSteps || (!isAnswer && blocks.length > 0 && !providerError && !truncated);
|
| 21469 |
+
|
| 21470 |
+
const copyContent = () => {
|
| 21471 |
+
copyText(textContent).then(() => {
|
| 21472 |
+
@@ -803,123 +733,59 @@ function AssistantMessageView({
|
| 21473 |
|
| 21474 |
if (blocks.length === 0 && !isStreaming && !providerError && !truncated) return null;
|
| 21475 |
|
|
|
|
| 21488 |
- style={{ marginBottom: 16 }}
|
| 21489 |
- onMouseEnter={() => setHovered(true)}
|
| 21490 |
- onMouseLeave={() => setHovered(false)}
|
| 21491 |
+
+ className={`turn-reply${asStep ? " is-step" : ""}`}
|
| 21492 |
>
|
| 21493 |
- {/* Model label */}
|
| 21494 |
- <div
|
|
|
|
| 21618 |
)}
|
| 21619 |
</div>
|
| 21620 |
)}
|
| 21621 |
+
@@ -928,51 +794,29 @@ function AssistantMessageView({
|
| 21622 |
<TurnWrittenFiles files={writtenFiles} onOpenFile={onOpenFile} />
|
| 21623 |
)}
|
| 21624 |
|
|
|
|
| 21631 |
- </div>
|
| 21632 |
- )}
|
| 21633 |
- {textContent && !isStreaming && (
|
| 21634 |
+
- <button
|
| 21635 |
+
- onClick={copyContent}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21636 |
- title={t("i18n.copyMessage")}
|
| 21637 |
- style={{
|
| 21638 |
- display: "flex", alignItems: "center", gap: 4,
|
|
|
|
| 21649 |
- }}
|
| 21650 |
- onMouseEnter={(e) => { if (!copied) e.currentTarget.style.color = "var(--accent)"; }}
|
| 21651 |
- onMouseLeave={(e) => { if (!copied) e.currentTarget.style.color = "var(--text-dim)"; }}
|
| 21652 |
+
- >
|
|
|
|
|
|
|
| 21653 |
- {copied ? (
|
| 21654 |
- <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
| 21655 |
- <polyline points="20 6 9 17 4 12" />
|
|
|
|
| 21659 |
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
| 21660 |
- <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
| 21661 |
- </svg>
|
| 21662 |
+
+ {/* Only an answer has an actions row; Copy shows with the rest of it, on hover or focus
|
| 21663 |
+
+ (always on a touch screen, which cannot hover). */}
|
| 21664 |
+
+ {!asStep && !isStreaming && isAnswer && (
|
| 21665 |
+
+ <div className="message-actions">
|
| 21666 |
+
+ <div className="message-actions-reveal">
|
| 21667 |
+
+ <button
|
| 21668 |
+
+ type="button"
|
| 21669 |
+
+ className="ui-btn ui-btn--xs ui-btn--icon"
|
| 21670 |
+
+ data-done={copied || undefined}
|
| 21671 |
+
+ onClick={copyContent}
|
| 21672 |
+
+ title={t("i18n.copyMessage")}
|
| 21673 |
+
+ aria-label={copied ? t("i18n.copied") : t("i18n.copyMessage")}
|
| 21674 |
+
+ >
|
| 21675 |
+
+ <CarbonIcon icon={copied ? Carbon.Checkmark : Carbon.Copy} size={14} />
|
| 21676 |
+
+ </button>
|
| 21677 |
+
+ {modelName && <span className="message-meta">{modelName}</span>}
|
| 21678 |
+
+ {shownUsage && formatUsageShort(shownUsage) && (
|
| 21679 |
+
+ <span className="message-meta" title={formatUsage(shownUsage)}>{formatUsageShort(shownUsage)}</span>
|
| 21680 |
+
)}
|
| 21681 |
- {copied ? t("i18n.copied") : t("i18n.copy")}
|
| 21682 |
+
- </button>
|
|
|
|
| 21683 |
- )}
|
| 21684 |
- {time && !isStreaming && (
|
| 21685 |
- <span style={{ fontSize: 10, color: "var(--text-dim)", marginLeft: "auto" }}>{time}</span>
|
| 21686 |
- )}
|
| 21687 |
- </div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21688 |
+ {time && <span className="message-meta">{time}</span>}
|
| 21689 |
+ </div>
|
| 21690 |
+ </div>
|
|
|
|
| 21692 |
</div>
|
| 21693 |
);
|
| 21694 |
}
|
| 21695 |
+
@@ -988,7 +832,7 @@ function BlockView({ block, searchTarget, toolResults, isStreaming, streamingDur
|
| 21696 |
const tc = block as ToolCallContent;
|
| 21697 |
const result = toolResults?.get(tc.toolCallId);
|
| 21698 |
const duration = toolCallDurations?.get(tc.toolCallId);
|
|
|
|
| 21701 |
}
|
| 21702 |
return null;
|
| 21703 |
}
|
| 21704 |
+
@@ -1052,62 +896,34 @@ export function ThinkingBlock({ block, duration, sessionId, entryId, blockIndex
|
| 21705 |
}, [expanded, block.deferred, content, sessionId, entryId, blockIndex]);
|
| 21706 |
|
| 21707 |
return (
|
|
|
|
| 21788 |
</div>
|
| 21789 |
);
|
| 21790 |
}
|
| 21791 |
+
@@ -1137,7 +953,7 @@ export function formatToolDuration(ms: number): string {
|
| 21792 |
return `${Math.floor(minutes / 60)}h ${minutes % 60}m ${remainder}s`;
|
| 21793 |
}
|
| 21794 |
|
|
|
|
| 21797 |
const { t } = useI18n();
|
| 21798 |
const [expanded, setExpanded] = useState(() => isToolCallExpanded(block.toolCallId));
|
| 21799 |
const toggleExpanded = () => {
|
| 21800 |
+
@@ -1146,6 +962,7 @@ function ToolCallBlock({ block, result, duration, onOpenSubagent }: { block: Too
|
| 21801 |
setExpanded(next);
|
| 21802 |
};
|
| 21803 |
const inputStr = getWrittenFileText(block) ?? getToolCallInputText(block);
|
|
|
|
| 21805 |
const isStreamingInput = block.rawInput !== undefined;
|
| 21806 |
const isEditTool = isEditToolName(block.toolName);
|
| 21807 |
const resultDiff = result && !result.isError ? getResultDiff(result) : null;
|
| 21808 |
+
@@ -1175,137 +992,154 @@ function ToolCallBlock({ block, result, duration, onOpenSubagent }: { block: Too
|
| 21809 |
|| (isApplyPatchToolName(block.toolName) && applyPatchResultHasFailures(result?.details));
|
| 21810 |
const subagent = isSubagentToolDetails(result?.details) ? result.details : null;
|
| 21811 |
const codemodeCallCount = codemode ? codemode.calls.length + codemode.omitted : 0;
|
|
|
|
| 22054 |
interface ResultDiff {
|
| 22055 |
text: string;
|
| 22056 |
}
|
| 22057 |
+
@@ -1316,7 +1150,6 @@ function PairedDiffResult({ diff }: {
|
| 22058 |
return (
|
| 22059 |
<div
|
| 22060 |
style={{
|
|
|
|
| 22062 |
background: "var(--bg)",
|
| 22063 |
}}
|
| 22064 |
>
|
| 22065 |
+
@@ -1325,148 +1158,16 @@ function PairedDiffResult({ diff }: {
|
| 22066 |
);
|
| 22067 |
}
|
| 22068 |
|
|
|
|
| 22218 |
}
|
| 22219 |
|
| 22220 |
function PatchTextView({ text }: { text: string }) {
|
| 22221 |
+
@@ -1483,12 +1184,12 @@ function PatchTextView({ text }: { text: string }) {
|
| 22222 |
const bg =
|
| 22223 |
kind === "added" ? "rgba(34,197,94,0.12)" :
|
| 22224 |
kind === "removed" ? "rgba(248,113,113,0.13)" :
|
|
|
|
| 22233 |
"var(--text)";
|
| 22234 |
|
| 22235 |
return (
|
| 22236 |
+
@@ -1502,7 +1203,7 @@ function PatchTextView({ text }: { text: string }) {
|
| 22237 |
: kind === "removed"
|
| 22238 |
? "3px solid #f87171"
|
| 22239 |
: kind === "hunk"
|
|
|
|
| 22242 |
: "3px solid transparent",
|
| 22243 |
}}
|
| 22244 |
>
|
| 22245 |
+
@@ -1578,16 +1279,16 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
| 22246 |
return typeof value === "object" && value !== null && !Array.isArray(value);
|
| 22247 |
}
|
| 22248 |
|
|
|
|
| 22261 |
}}
|
| 22262 |
>
|
| 22263 |
{images.map((image, index) => {
|
| 22264 |
+
@@ -1606,9 +1307,10 @@ function ResultImages({ images, isError }: { images: ImageContent[]; isError: bo
|
| 22265 |
loading="lazy"
|
| 22266 |
style={{
|
| 22267 |
display: "block",
|
|
|
|
| 22275 |
objectFit: "contain",
|
| 22276 |
border: "1px solid var(--border)",
|
| 22277 |
}}
|
| 22278 |
+
@@ -1620,6 +1322,67 @@ function ResultImages({ images, isError }: { images: ImageContent[]; isError: bo
|
| 22279 |
);
|
| 22280 |
}
|
| 22281 |
|
|
|
|
| 22343 |
function PairedResult({ text, isEmpty, isError }: {
|
| 22344 |
text: string;
|
| 22345 |
isEmpty: boolean;
|
| 22346 |
+
@@ -1629,8 +1392,7 @@ function PairedResult({ text, isEmpty, isError }: {
|
| 22347 |
return (
|
| 22348 |
<div
|
| 22349 |
style={{
|
|
|
|
| 22353 |
}}
|
| 22354 |
>
|
| 22355 |
<pre
|
| 22356 |
+
@@ -1668,7 +1430,7 @@ function CompactionMessageView({ message }: { message: CustomMessage }) {
|
| 22357 |
<div
|
| 22358 |
style={{
|
| 22359 |
border: "1px solid var(--border)",
|
|
|
|
| 22362 |
overflow: "hidden",
|
| 22363 |
background: "var(--bg)",
|
| 22364 |
}}
|
| 22365 |
+
@@ -1710,9 +1472,7 @@ function CompactionMessageView({ message }: { message: CustomMessage }) {
|
| 22366 |
textAlign: "left",
|
| 22367 |
}}
|
| 22368 |
>
|
|
|
|
| 22373 |
<span style={{ minWidth: 0 }}>
|
| 22374 |
<span style={{ display: "block", fontSize: "calc(15px + var(--chat-font-size-offset, 0px))", fontWeight: 700, lineHeight: 1.35 }}>
|
| 22375 |
{t("i18n.conversationCompacted")}
|
| 22376 |
+
@@ -1806,7 +1566,7 @@ function CustomMessageView({ message, cwd, onOpenFile }: { message: CustomMessag
|
| 22377 |
<div
|
| 22378 |
style={{
|
| 22379 |
border: "1px solid var(--border)",
|
|
|
|
| 22382 |
overflow: "hidden",
|
| 22383 |
background: "var(--bg)",
|
| 22384 |
}}
|
| 22385 |
+
@@ -1852,9 +1612,7 @@ function CustomMessageView({ message, cwd, onOpenFile }: { message: CustomMessag
|
| 22386 |
</span>
|
| 22387 |
)}
|
| 22388 |
{time && <span style={{ marginLeft: "auto", color: "var(--text-dim)", fontSize: 10, flexShrink: 0 }}>{time}</span>}
|
|
|
|
| 22393 |
</button>
|
| 22394 |
</div>
|
| 22395 |
|
| 22396 |
+
@@ -1872,7 +1630,7 @@ function CustomMessageView({ message, cwd, onOpenFile }: { message: CustomMessag
|
| 22397 |
<img
|
| 22398 |
src={src}
|
| 22399 |
alt=""
|
|
|
|
| 22402 |
/>
|
| 22403 |
</ImagePreview>
|
| 22404 |
);
|
| 22405 |
+
@@ -1999,6 +1757,32 @@ function getWrittenFileText(block: ToolCallContent): string | null {
|
| 22406 |
return Object.keys(block.input).every((key) => WRITE_VIEW_KEYS.has(key)) ? content : null;
|
| 22407 |
}
|
| 22408 |
|
|
|
|
| 22435 |
function formatCustomType(type: string): string {
|
| 22436 |
return type || "extension";
|
| 22437 |
}
|
| 22438 |
+
@@ -2021,13 +1805,16 @@ function getToolPreview(block: ToolCallContent): string {
|
| 22439 |
return String(first).slice(0, 120);
|
| 22440 |
}
|
| 22441 |
|
|
|
|
| 22695 |
onSelect={() => choose(option)}
|
| 22696 |
star={onSetDefault ? {
|
| 22697 |
diff --git a/components/ModelsConfig.tsx b/components/ModelsConfig.tsx
|
| 22698 |
+
index 9f6b556..d17e0cf 100644
|
| 22699 |
--- a/components/ModelsConfig.tsx
|
| 22700 |
+++ b/components/ModelsConfig.tsx
|
| 22701 |
@@ -54,6 +54,8 @@ import { providerBadgeLabel } from "./enabled-models-helpers";
|
|
|
|
| 22920 |
Open the verification page and enter this code:
|
| 22921 |
</p>
|
| 22922 |
- <div style={{ padding: "8px 10px", background: "var(--bg)", border: "1px solid var(--border)", borderRadius: 5, color: "var(--text)", fontSize: 16, fontWeight: 700, fontFamily: "var(--font-mono)", letterSpacing: 0 }}>
|
| 22923 |
+
+ <div style={{ padding: "8px 10px", background: "var(--bg)", border: "1px solid var(--border)", borderRadius: "var(--radius-sm)", color: "var(--text)", fontSize: 16, fontWeight: 700, fontFamily: "var(--font-mono)" }}>
|
| 22924 |
{loginState.userCode}
|
| 22925 |
</div>
|
| 22926 |
<p style={{ margin: 0, fontSize: 11, color: "var(--text-dim)", lineHeight: 1.5 }}>
|
|
|
|
| 31081 |
- **Notices.** MCP off, with its reason. Otherwise `hostInactive`: an open session whose `/mcp` is another extension's, naming its folder and that extension (only a session can tell). A project folder the route refuses (`cwd-*`) does not hide the global servers: `loadMcpOverview()` loads again without the cwd and the Project group says "Not listed" with the reason. Trust is reported only when the project has a `.pi/mcp.json` or a problem with one: untrusted (`ConfigTrustNotice`), an inherited `false` naming its folder, and inherited trust as "Trusted through <path>", the only place that trust is visible. A folder that requires no trust gets no trust notice unless a decision marks it untrusted: a dangling `.pi/mcp.json` link is listed (`link-dangling`), but the SDK's `existsSync` finds nothing to trust, so Trust would only meet `trust-not-required`. File problems go to the footer (`ConfigFooterStatus`). A load ends after `MCP_OVERVIEW_TIMEOUT_MS` (15 s, both requests together), since Refresh is disabled while one runs.
|
| 31082 |
- **The detail pane shows only what GET sends:** the masked command line (`mcpServerTarget()`), working directory, env and header **names** (each only when set), the `!command` fields and host variables (left out for a refused entry, which never connects; kept for a disabled one), the Sign-in row (**Settings › MCP sign-in** in [mcp-test-sign-in.md](mcp-test-sign-in.md)), the exposure, the file. The Code mode pane holds the choice, the sandbox and `-builtin:codemode`.
|
| 31083 |
diff --git a/docs/agents/sessions.md b/docs/agents/sessions.md
|
| 31084 |
+
index 42e1816..85d70bc 100644
|
| 31085 |
--- a/docs/agents/sessions.md
|
| 31086 |
+++ b/docs/agents/sessions.md
|
| 31087 |
@@ -38,7 +38,7 @@ On mount `useAgentSession` loads the history, then `GET /api/sessions/[id]/state
|
|
|
|
| 31098 |
- Spacing: groups sit right on each other, open or folded (collapsed ones at a 28px pitch); a gap between two of them only pushed the next header away. An 8px spacer row parts the pinned section from the groups (`pinned-spacer`) and the groups from the footer (`footer-spacer`, only when there are groups; a pinned section with no group has the one spacer before the footer).
|
| 31099 |
- Heights (`SIDEBAR_ROW_HEIGHTS`) keep one rhythm: session rows, every header (a project's, the pinned section's, the archive's projects), the empty group's note and the footer's two links are 28px (44px on a phone, a finger's minimum, headers included); only "show more" is a step shorter (26px, 36px). Every control in a row fits it with 2px to spare (⋯ and archive, the rename field and a delete confirmation's buttons 24px, a header's + and ⋯ 22px; 32px on a phone; `SessionTree.test.mjs` checks it), and the drag ghost (22px) stays shorter than a header.
|
| 31100 |
-- Rows: nothing before the title; the right column shows the awaiting-input dot (amber, breathing), else the running spinner, else the unread dot, else the short time (a pinned row's project, an archived row's archive time). A running row keeps its spinner beside ⋯ on hover. Hover and selection are a rounded box inset from both edges; titles are 12px `--text` (the file tree's size), the selected one 500, and the selected row (in the archive view too) has a 3px accent bar on its box's left edge, 7px clear of its top and bottom (a 14px bar; 12px on a phone, 20px), a `::before` of the absolutely positioned row, so its rounded corners never clip it; a delete confirmation's red frame has none. A worktree's branch is meta in the rows' own type (11px dim, a 10px `BranchIcon` kept inline so the ellipsis still cuts the name; the tooltip keeps `⑂`), as a pinned row's project is (no code type). A classic scrollbar takes width on the right only (Chromium's and Safari's styled one is 10px, 6px on a coarse pointer; an overlay one none): `SessionTree` measures it into `--session-tree-scrollbar`, and the right inset shrinks by it, so rows end 6px in or on the gutter's edge, whichever is further in, while the left inset stays 6px (moving it too put the tree's two lines 4px right of everything else's in Chromium). It also puts the width on the sidebar's root (only a tree with a box: not one under the archive view or in a hidden tab), where the files below the sessions read it for their right edge. A row's time (or spinner, dot) ends 4px inside its box, where a folded group's summary ends: neither the row nor its always-rendered actions span has a gap, each shown action brings its own 2px (`margin-left`), so the hidden ones leave the time on the edge. The list keeps the scrollbar's room even when everything fits (`scrollbar-gutter: stable`; `overflow-y: scroll` in Safari before 18.2), so rows never narrow when it starts to scroll.
|
| 31101 |
+
+- Rows: nothing before the title; the right column shows the awaiting-input dot (amber, breathing), else the running spinner, else the unread dot, else the short time (a pinned row's project, an archived row's archive time). A running row keeps its spinner beside ⋯ on hover. In the transcript only an answer (text without tool calls) has an actions row, and its Copy shows on hover or focus with the model, usage and time (always on a touch screen); a whitespace-only text block (DeepSeek sends "\n\n" beside each tool call) renders nothing, and a running turn's messages are drawn as steps. Steps are one 4px rhythm, within a message and between messages (set on the messages' wrappers), folded or live; the answer sits 8px under its steps, open or closed. A live row has no hover Archive button (Archive is in its ⋯ menu); the archive view's rows keep Restore one click away. The toolbar's search opens the command bar (`components/SearchCommandBar.tsx`, also ⌘K / Ctrl+K outside a terminal): recent top-level conversations until something is typed, then `/api/sessions/search` matches with their text; arrows move, Enter opens, Esc closes. Hover and selection are a rounded box inset from both edges; titles are 12px `--text` (the file tree's size), the selected one 500, and the selected row (in the archive view too) has a 3px accent bar on its box's left edge, 7px clear of its top and bottom (a 14px bar; 12px on a phone, 20px), a `::before` of the absolutely positioned row, so its rounded corners never clip it; a delete confirmation's red frame has none. A worktree's branch is meta in the rows' own type (11px dim, a 10px `BranchIcon` kept inline so the ellipsis still cuts the name; the tooltip keeps `⑂`), as a pinned row's project is (no code type). A classic scrollbar takes width on the right only (Chromium's and Safari's styled one is 10px, 6px on a coarse pointer; an overlay one none): `SessionTree` measures it into `--session-tree-scrollbar`, and the right inset shrinks by it, so rows end 6px in or on the gutter's edge, whichever is further in, while the left inset stays 6px (moving it too put the tree's two lines 4px right of everything else's in Chromium). It also puts the width on the sidebar's root (only a tree with a box: not one under the archive view or in a hidden tab), where the files below the sessions read it for their right edge. A row's time (or spinner, dot) ends 4px inside its box, where a folded group's summary ends: neither the row nor its always-rendered actions span has a gap, each shown action brings its own 2px (`margin-left`), so the hidden ones leave the time on the edge. The list keeps the scrollbar's room even when everything fits (`scrollbar-gutter: stable`; `overflow-y: scroll` in Safari before 18.2), so rows never narrow when it starts to scroll.
|
| 31102 |
- Row keys are unique and stable (`session:<context>:<rootId>`, `group:<key>`, `more:<key>`, `empty:<key>`, and fixed keys for the pinned header, the two spacers (`pinned-spacer`, `footer-spacer`) and the footer); focus tracking and `keepMounted` go by them.
|
| 31103 |
- Menus (`SidebarMenu`) and the toast live in `SessionSidebar`, above the virtualized rows, so a row scrolled away keeps its open menu (the project/worktree picker of the files tab and of the composer's bar uses `SidebarMenu` for its own two menus); the row with focus, a menu, a rename or a delete confirmation stays mounted (`keepMounted`). Menus are portaled to `document.body`: the mobile drawer's `transform` would pin a `position: fixed` menu to the drawer. The archive view is a second tree; the main one stays mounted underneath, the two in one grid cell (`.sidebar-sessions-views`, `grid-area: 1 / 1`; the archive opaque in `--bg-panel` and stacked above the tree for good, `position: relative; z-index: 1`: the tree's rows are positioned and paint over an unpositioned view, which only its animation lifted, so the frame its entrance ended in flashed the tree). Going in and out moves, in both layouts and on phones (`useSidebarMotion()`, `archiveViewState()` in `hooks/useSidebarMotion.ts`; `setArchiveView()` starts it on a change only, and View, Back and a fork's open all go through it): in, the archive slides from 14px right and fades in over the tree (`.is-entering`, 180ms ease-out), the tree `inert` under it until the view is in, then `hidden` as before; out, the tree is back at once and the archive slides and fades out to the right over it (`.is-leaving`, 180ms ease-in, `forwards`), still mounted with its rows (`archiveRows` is built while `archiveMounted`, else it would leave empty), `inert` and `pointer-events: none`, so nothing in it is clicked twice and a click lands on the tree, until its own `animationend` (not one bubbling from a row) unmounts it. A phase that gets no end event (a sidebar not shown: a hidden tab, a closed drawer) ends 300 ms after its start (`SIDEBAR_MOTION_MS` + `SIDEBAR_MOTION_GRACE_MS`). Under `prefers-reduced-motion` no phase starts and the CSS has no animation: the views swap at once, as before.
|
| 31104 |
- Focus: a menu returns it to the opener it was given when it opened (the parent drops `returnFocusTo` with the menu); a row's ⋯ or a group's buttons are `display: none` by then unless hovered, selected or focused, so `menuFocusReturnTarget()` falls back to the row's first focusable control (its main link or transient-session button, a group's toggle). Controls that go away under the keyboard hand focus on: the archive's Back button when it opens, the footer link (or the selected tab, or with no tabs the toolbar row's search) after Back, the family's row after a toast's Undo or a delete Cancel (`focusIfLost()`, only when focus fell to `<body>`). The archive's two moves make the view they leave `inert` before it is hidden or gone, and Chromium blurs an inert element only at its next frame, after the effect that moves focus has run: `focusIfHidden()` counts focus on something without a box or inside `[inert]` as lost too.
|
|
|
|
| 43824 |
+ </g>
|
| 43825 |
+</svg>
|
| 43826 |
diff --git a/public/offline.html b/public/offline.html
|
| 43827 |
+
index 0dac4b2..510150c 100644
|
| 43828 |
--- a/public/offline.html
|
| 43829 |
+++ b/public/offline.html
|
| 43830 |
@@ -3,14 +3,38 @@
|
|
|
|
| 43883 |
}
|
| 43884 |
|
| 43885 |
main {
|
| 43886 |
+
@@ -30,53 +58,65 @@
|
| 43887 |
text-align: center;
|
| 43888 |
}
|
| 43889 |
|
|
|
|
| 43906 |
+ margin: 0 0 6px;
|
| 43907 |
+ font-size: 18px;
|
| 43908 |
+ font-weight: 600;
|
|
|
|
| 43909 |
+ line-height: 1.3;
|
| 43910 |
}
|
| 43911 |
|