forkit-connect 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/launcher.js CHANGED
@@ -793,7 +793,7 @@ function buildDiscovery(service) {
793
793
  return {
794
794
  id: entry.item_id,
795
795
  kind: 'runtime',
796
- selector: runtime.discoveryHash,
796
+ selector: runtimeGaid,
797
797
  inboxGroup: group,
798
798
  name: runtime.runtime_name,
799
799
  subtitle: `${formatRuntimeType(runtime.runtime_type)} • ${formatEndpointLabel(runtime.source_endpoint_label)}`,
@@ -1680,6 +1680,379 @@ function renderLauncherHtml(launcherToken) {
1680
1680
  z-index: 130;
1681
1681
  }
1682
1682
 
1683
+ .quick-review-anchor {
1684
+ position: relative;
1685
+ flex: 0 1 344px;
1686
+ min-width: 0;
1687
+ }
1688
+
1689
+ .quick-review-chip {
1690
+ width: 100%;
1691
+ min-height: 74px;
1692
+ border: 1px solid rgba(241, 235, 223, 0.22);
1693
+ border-radius: 24px;
1694
+ background:
1695
+ radial-gradient(circle at 100% 0%, rgba(106, 167, 171, 0.2), transparent 36%),
1696
+ linear-gradient(135deg, rgba(30, 25, 47, 0.97) 0%, rgba(17, 20, 31, 0.985) 100%);
1697
+ color: #f7efe4;
1698
+ display: flex;
1699
+ align-items: center;
1700
+ gap: 14px;
1701
+ padding: 14px 16px;
1702
+ cursor: pointer;
1703
+ box-shadow:
1704
+ 0 22px 38px rgba(7, 10, 24, 0.3),
1705
+ inset 0 1px 0 rgba(255,255,255,0.08);
1706
+ text-align: left;
1707
+ transition: transform 160ms ease, border-color 160ms ease, box-shadow 160ms ease;
1708
+ }
1709
+
1710
+ .quick-review-chip:hover {
1711
+ border-color: rgba(157, 238, 245, 0.34);
1712
+ transform: translateY(-1px);
1713
+ box-shadow:
1714
+ 0 26px 46px rgba(7, 10, 24, 0.34),
1715
+ inset 0 1px 0 rgba(255,255,255,0.1);
1716
+ }
1717
+
1718
+ .quick-review-chip:focus-visible,
1719
+ .quick-review-primary:focus-visible,
1720
+ .quick-review-options-button:focus-visible,
1721
+ .quick-review-field select:focus-visible,
1722
+ .quick-review-options-menu button:focus-visible {
1723
+ outline: 2px solid rgba(157, 238, 245, 0.72);
1724
+ outline-offset: 2px;
1725
+ }
1726
+
1727
+ .quick-review-count {
1728
+ width: 34px;
1729
+ height: 34px;
1730
+ border-radius: 12px;
1731
+ display: inline-flex;
1732
+ align-items: center;
1733
+ justify-content: center;
1734
+ font-family: "Sora", sans-serif;
1735
+ font-size: 0.92rem;
1736
+ font-weight: 700;
1737
+ color: #0b1220;
1738
+ background: linear-gradient(135deg, rgba(157, 238, 245, 0.96) 0%, rgba(244, 147, 85, 0.92) 100%);
1739
+ flex: 0 0 auto;
1740
+ }
1741
+
1742
+ .quick-review-chip-copy {
1743
+ min-width: 0;
1744
+ display: flex;
1745
+ flex-direction: column;
1746
+ gap: 3px;
1747
+ flex: 1 1 auto;
1748
+ }
1749
+
1750
+ .quick-review-chip-title {
1751
+ font-family: "Sora", sans-serif;
1752
+ font-size: 0.98rem;
1753
+ line-height: 1.15;
1754
+ letter-spacing: -0.01em;
1755
+ color: #fff7ea;
1756
+ white-space: nowrap;
1757
+ overflow: hidden;
1758
+ text-overflow: ellipsis;
1759
+ }
1760
+
1761
+ .quick-review-chip-meta {
1762
+ font-size: 0.8rem;
1763
+ line-height: 1.25;
1764
+ color: rgba(241, 235, 223, 0.72);
1765
+ white-space: nowrap;
1766
+ overflow: hidden;
1767
+ text-overflow: ellipsis;
1768
+ }
1769
+
1770
+ .quick-review-chip-caret {
1771
+ font-size: 0.82rem;
1772
+ color: rgba(241, 235, 223, 0.7);
1773
+ flex: 0 0 auto;
1774
+ }
1775
+
1776
+ .quick-review-panel-shell {
1777
+ position: absolute;
1778
+ top: calc(100% + 10px);
1779
+ right: 0;
1780
+ width: min(420px, calc(100vw - 48px));
1781
+ z-index: 260;
1782
+ }
1783
+
1784
+ .quick-review-card {
1785
+ display: grid;
1786
+ gap: 14px;
1787
+ padding: 18px;
1788
+ border-radius: 26px;
1789
+ border: 1px solid rgba(241, 235, 223, 0.16);
1790
+ background:
1791
+ radial-gradient(circle at 100% 0%, rgba(106, 167, 171, 0.18), transparent 38%),
1792
+ linear-gradient(135deg, rgba(24, 22, 38, 0.98) 0%, rgba(17, 19, 30, 0.985) 100%);
1793
+ box-shadow:
1794
+ 0 28px 56px rgba(7, 10, 24, 0.42),
1795
+ inset 0 1px 0 rgba(255,255,255,0.07);
1796
+ color: #f7efe4;
1797
+ }
1798
+
1799
+ .quick-review-summary-grid {
1800
+ display: grid;
1801
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1802
+ gap: 10px;
1803
+ }
1804
+
1805
+ .quick-review-summary-pill {
1806
+ display: grid;
1807
+ gap: 4px;
1808
+ padding: 11px 12px;
1809
+ border-radius: 16px;
1810
+ border: 1px solid rgba(241, 235, 223, 0.1);
1811
+ background: rgba(255,255,255,0.045);
1812
+ }
1813
+
1814
+ .quick-review-summary-label {
1815
+ font-size: 0.68rem;
1816
+ text-transform: uppercase;
1817
+ letter-spacing: 0.12em;
1818
+ font-weight: 700;
1819
+ color: rgba(241, 235, 223, 0.55);
1820
+ }
1821
+
1822
+ .quick-review-summary-value {
1823
+ font-size: 0.86rem;
1824
+ line-height: 1.35;
1825
+ color: #fff7ea;
1826
+ font-weight: 600;
1827
+ }
1828
+
1829
+ .quick-review-topline {
1830
+ display: flex;
1831
+ align-items: flex-start;
1832
+ justify-content: space-between;
1833
+ gap: 10px;
1834
+ }
1835
+
1836
+ .quick-review-kicker {
1837
+ margin: 0;
1838
+ font-size: 0.72rem;
1839
+ text-transform: uppercase;
1840
+ letter-spacing: 0.12em;
1841
+ font-weight: 700;
1842
+ color: rgba(157, 238, 245, 0.84);
1843
+ }
1844
+
1845
+ .quick-review-title {
1846
+ margin: 4px 0 0;
1847
+ font-family: "Sora", sans-serif;
1848
+ font-size: 1.22rem;
1849
+ line-height: 1.12;
1850
+ letter-spacing: -0.02em;
1851
+ color: #fff8ef;
1852
+ }
1853
+
1854
+ .quick-review-badge {
1855
+ flex: 0 0 auto;
1856
+ display: inline-flex;
1857
+ align-items: center;
1858
+ gap: 6px;
1859
+ min-height: 36px;
1860
+ padding: 0 14px;
1861
+ border-radius: 999px;
1862
+ border: 1px solid rgba(157, 238, 245, 0.26);
1863
+ background: rgba(55, 68, 86, 0.42);
1864
+ color: rgba(157, 238, 245, 0.96);
1865
+ font-family: "Sora", sans-serif;
1866
+ font-size: 0.88rem;
1867
+ font-weight: 700;
1868
+ letter-spacing: 0.08em;
1869
+ text-transform: uppercase;
1870
+ }
1871
+
1872
+ .quick-review-meta,
1873
+ .quick-review-detail {
1874
+ margin: 0;
1875
+ font-size: 0.92rem;
1876
+ line-height: 1.5;
1877
+ color: rgba(241, 235, 223, 0.78);
1878
+ }
1879
+
1880
+ .quick-review-status {
1881
+ border-radius: 16px;
1882
+ padding: 12px 14px;
1883
+ border: 1px solid rgba(202, 188, 255, 0.12);
1884
+ background: rgba(255,255,255,0.04);
1885
+ color: rgba(241, 235, 223, 0.8);
1886
+ font-size: 0.88rem;
1887
+ line-height: 1.45;
1888
+ }
1889
+
1890
+ .quick-review-status.ok {
1891
+ color: #c7ffe4;
1892
+ border-color: rgba(63, 208, 143, 0.3);
1893
+ background: rgba(63, 208, 143, 0.12);
1894
+ }
1895
+
1896
+ .quick-review-status.warn {
1897
+ color: #ffe0be;
1898
+ border-color: rgba(244, 147, 85, 0.28);
1899
+ background: rgba(244, 147, 85, 0.12);
1900
+ }
1901
+
1902
+ .quick-review-status.error {
1903
+ color: #ffd1d7;
1904
+ border-color: rgba(255, 108, 134, 0.28);
1905
+ background: rgba(255, 108, 134, 0.12);
1906
+ }
1907
+
1908
+ .quick-review-scope {
1909
+ display: grid;
1910
+ gap: 10px;
1911
+ }
1912
+
1913
+ .quick-review-scope-grid {
1914
+ display: grid;
1915
+ grid-template-columns: 1fr 1fr;
1916
+ gap: 10px;
1917
+ }
1918
+
1919
+ .quick-review-field {
1920
+ display: grid;
1921
+ gap: 6px;
1922
+ }
1923
+
1924
+ .quick-review-field label {
1925
+ font-size: 0.74rem;
1926
+ text-transform: uppercase;
1927
+ letter-spacing: 0.08em;
1928
+ font-weight: 700;
1929
+ color: rgba(241, 235, 223, 0.62);
1930
+ }
1931
+
1932
+ .quick-review-field select {
1933
+ appearance: none;
1934
+ width: 100%;
1935
+ min-height: 46px;
1936
+ border-radius: 14px;
1937
+ border: 1px solid rgba(202, 188, 255, 0.16);
1938
+ background: rgba(23, 19, 48, 0.66);
1939
+ color: #fff8ef;
1940
+ padding: 0 14px;
1941
+ font-size: 0.92rem;
1942
+ outline: none;
1943
+ }
1944
+
1945
+ .quick-review-actions {
1946
+ display: grid;
1947
+ grid-template-columns: minmax(0, 1fr) auto;
1948
+ gap: 10px;
1949
+ align-items: start;
1950
+ }
1951
+
1952
+ .quick-review-primary {
1953
+ min-height: 52px;
1954
+ border: 1px solid rgba(157, 238, 245, 0.2);
1955
+ border-radius: 16px;
1956
+ background: linear-gradient(135deg, rgba(157, 238, 245, 0.92) 0%, rgba(244, 147, 85, 0.88) 100%);
1957
+ color: #1e1638;
1958
+ font-family: "Sora", sans-serif;
1959
+ font-size: 0.95rem;
1960
+ font-weight: 700;
1961
+ cursor: pointer;
1962
+ padding: 0 16px;
1963
+ }
1964
+
1965
+ .quick-review-primary:disabled,
1966
+ .quick-review-options-button:disabled {
1967
+ opacity: 0.45;
1968
+ cursor: not-allowed;
1969
+ }
1970
+
1971
+ .quick-review-options-wrap {
1972
+ position: relative;
1973
+ }
1974
+
1975
+ .quick-review-options-button {
1976
+ min-height: 52px;
1977
+ padding: 0 16px;
1978
+ border-radius: 16px;
1979
+ border: 1px solid rgba(241, 235, 223, 0.14);
1980
+ background: rgba(255,255,255,0.05);
1981
+ color: #fff7ea;
1982
+ font-size: 0.92rem;
1983
+ font-weight: 600;
1984
+ cursor: pointer;
1985
+ min-width: 122px;
1986
+ transition: background 160ms ease, border-color 160ms ease;
1987
+ }
1988
+
1989
+ .quick-review-options-button:hover {
1990
+ background: rgba(255,255,255,0.08);
1991
+ border-color: rgba(157, 238, 245, 0.22);
1992
+ }
1993
+
1994
+ .quick-review-options-menu {
1995
+ position: absolute;
1996
+ top: calc(100% + 8px);
1997
+ right: 0;
1998
+ min-width: 214px;
1999
+ display: grid;
2000
+ gap: 6px;
2001
+ padding: 10px;
2002
+ border-radius: 18px;
2003
+ border: 1px solid rgba(241, 235, 223, 0.12);
2004
+ background: rgba(19, 20, 31, 0.98);
2005
+ box-shadow: 0 22px 44px rgba(7, 10, 24, 0.38);
2006
+ }
2007
+
2008
+ .quick-review-options-menu button {
2009
+ min-height: 46px;
2010
+ border-radius: 12px;
2011
+ border: 1px solid transparent;
2012
+ background: rgba(255,255,255,0.04);
2013
+ color: #f7efe4;
2014
+ text-align: left;
2015
+ padding: 10px 12px;
2016
+ font-size: 0.9rem;
2017
+ font-weight: 600;
2018
+ cursor: pointer;
2019
+ }
2020
+
2021
+ .quick-review-options-menu button[hidden] {
2022
+ display: none;
2023
+ }
2024
+
2025
+ .quick-review-menu-copy {
2026
+ display: grid;
2027
+ gap: 2px;
2028
+ }
2029
+
2030
+ .quick-review-menu-label {
2031
+ font-size: 0.9rem;
2032
+ line-height: 1.25;
2033
+ color: #fff7ea;
2034
+ font-weight: 600;
2035
+ }
2036
+
2037
+ .quick-review-menu-meta {
2038
+ font-size: 0.76rem;
2039
+ line-height: 1.3;
2040
+ color: rgba(241, 235, 223, 0.58);
2041
+ }
2042
+
2043
+ .quick-review-options-menu button:hover {
2044
+ background: rgba(106, 167, 171, 0.14);
2045
+ border-color: rgba(106, 167, 171, 0.26);
2046
+ }
2047
+
2048
+ .quick-review-options-menu button.danger {
2049
+ color: #ffd4da;
2050
+ }
2051
+
2052
+ .quick-review-options-menu button.danger .quick-review-menu-label {
2053
+ color: #ffd4da;
2054
+ }
2055
+
1683
2056
  .status-chip {
1684
2057
  display: flex;
1685
2058
  align-items: center;
@@ -4315,13 +4688,144 @@ function renderLauncherHtml(launcherToken) {
4315
4688
  text-align: right;
4316
4689
  }
4317
4690
 
4318
- .discovery-review-button {
4319
- width: 100%;
4320
- margin-top: auto;
4321
- min-height: 54px;
4322
- border-radius: 18px;
4323
- border: 1px solid rgba(249, 193, 140, 0.35);
4324
- background: linear-gradient(90deg, rgba(111, 91, 198, 0.72), rgba(244, 147, 85, 0.72));
4691
+ .discovery-review-panel {
4692
+ display: flex;
4693
+ flex-direction: column;
4694
+ gap: 14px;
4695
+ padding: 16px;
4696
+ border-radius: 22px;
4697
+ border: 1px solid rgba(202, 188, 255, 0.16);
4698
+ background:
4699
+ radial-gradient(circle at 100% 0%, rgba(126,214,224,0.14), transparent 28%),
4700
+ linear-gradient(180deg, rgba(255,255,255,0.07), rgba(255,255,255,0.03));
4701
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.07);
4702
+ }
4703
+
4704
+ .discovery-review-kicker {
4705
+ margin: 0;
4706
+ font-size: 0.72rem;
4707
+ text-transform: uppercase;
4708
+ letter-spacing: 0.12em;
4709
+ font-weight: 700;
4710
+ color: rgba(157, 238, 245, 0.84);
4711
+ }
4712
+
4713
+ .discovery-review-title {
4714
+ margin: 0;
4715
+ font-family: "Sora", sans-serif;
4716
+ font-size: 1.22rem;
4717
+ line-height: 1.15;
4718
+ color: #fff8ef;
4719
+ }
4720
+
4721
+ .discovery-review-meta {
4722
+ margin: 0;
4723
+ color: rgba(241, 235, 223, 0.72);
4724
+ font-size: 0.92rem;
4725
+ line-height: 1.45;
4726
+ }
4727
+
4728
+ .discovery-review-detail {
4729
+ margin: 0;
4730
+ color: rgba(241, 235, 223, 0.84);
4731
+ font-size: 0.96rem;
4732
+ line-height: 1.55;
4733
+ }
4734
+
4735
+ .discovery-review-status {
4736
+ border-radius: 16px;
4737
+ padding: 12px 14px;
4738
+ border: 1px solid rgba(202, 188, 255, 0.12);
4739
+ background: rgba(255,255,255,0.04);
4740
+ color: rgba(241, 235, 223, 0.78);
4741
+ font-size: 0.9rem;
4742
+ line-height: 1.45;
4743
+ }
4744
+
4745
+ .discovery-review-status.ok {
4746
+ color: #c7ffe4;
4747
+ border-color: rgba(63, 208, 143, 0.3);
4748
+ background: rgba(63, 208, 143, 0.12);
4749
+ }
4750
+
4751
+ .discovery-review-status.warn {
4752
+ color: #ffe0be;
4753
+ border-color: rgba(244, 147, 85, 0.28);
4754
+ background: rgba(244, 147, 85, 0.12);
4755
+ }
4756
+
4757
+ .discovery-review-status.error {
4758
+ color: #ffd1d7;
4759
+ border-color: rgba(255, 108, 134, 0.28);
4760
+ background: rgba(255, 108, 134, 0.12);
4761
+ }
4762
+
4763
+ .discovery-review-scope {
4764
+ display: grid;
4765
+ gap: 12px;
4766
+ }
4767
+
4768
+ .discovery-review-scope-grid {
4769
+ display: grid;
4770
+ grid-template-columns: 1fr 1fr;
4771
+ gap: 10px;
4772
+ }
4773
+
4774
+ .discovery-review-field {
4775
+ display: grid;
4776
+ gap: 6px;
4777
+ }
4778
+
4779
+ .discovery-review-field label {
4780
+ font-size: 0.78rem;
4781
+ text-transform: uppercase;
4782
+ letter-spacing: 0.08em;
4783
+ font-weight: 700;
4784
+ color: rgba(241, 235, 223, 0.64);
4785
+ }
4786
+
4787
+ .discovery-review-field select {
4788
+ appearance: none;
4789
+ width: 100%;
4790
+ min-height: 48px;
4791
+ border-radius: 14px;
4792
+ border: 1px solid rgba(202, 188, 255, 0.16);
4793
+ background: rgba(23, 19, 48, 0.64);
4794
+ color: #fff8ef;
4795
+ padding: 0 14px;
4796
+ font-size: 0.94rem;
4797
+ outline: none;
4798
+ }
4799
+
4800
+ .discovery-review-actions {
4801
+ display: grid;
4802
+ grid-template-columns: 1fr 1fr;
4803
+ gap: 10px;
4804
+ }
4805
+
4806
+ .discovery-review-actions .primary {
4807
+ min-height: 52px;
4808
+ }
4809
+
4810
+ .discovery-review-actions .secondary {
4811
+ min-height: 52px;
4812
+ }
4813
+
4814
+ .discovery-review-actions .danger {
4815
+ min-height: 48px;
4816
+ border-radius: 14px;
4817
+ border: 1px solid rgba(255, 108, 134, 0.28);
4818
+ background: linear-gradient(180deg, rgba(110, 28, 46, 0.72), rgba(70, 18, 33, 0.88));
4819
+ color: #fff0f3;
4820
+ }
4821
+
4822
+ .discovery-review-button {
4823
+ width: 100%;
4824
+ margin-top: auto;
4825
+ min-height: 54px;
4826
+ border-radius: 18px;
4827
+ border: 1px solid rgba(249, 193, 140, 0.35);
4828
+ background: linear-gradient(90deg, rgba(111, 91, 198, 0.72), rgba(244, 147, 85, 0.72));
4325
4829
  color: #fff8ef;
4326
4830
  }
4327
4831
 
@@ -5818,6 +6322,79 @@ function renderLauncherHtml(launcherToken) {
5818
6322
  <div class="launch-scope-menu" id="project-select-menu" role="menu" hidden></div>
5819
6323
  </div>
5820
6324
 
6325
+ <div class="quick-review-anchor" id="quick-review-anchor" hidden>
6326
+ <button class="quick-review-chip" id="quick-review-toggle" type="button" aria-expanded="false" aria-controls="quick-review-panel">
6327
+ <span class="quick-review-count" id="quick-review-count">0</span>
6328
+ <span class="quick-review-chip-copy">
6329
+ <span class="quick-review-chip-title" id="quick-review-chip-title">No review needed</span>
6330
+ <span class="quick-review-chip-meta" id="quick-review-chip-meta">Everything looks clear</span>
6331
+ </span>
6332
+ <span class="quick-review-chip-caret">▾</span>
6333
+ </button>
6334
+ <div class="quick-review-panel-shell" id="quick-review-panel" hidden>
6335
+ <section class="quick-review-card">
6336
+ <div class="quick-review-topline">
6337
+ <div>
6338
+ <p class="quick-review-kicker" id="quick-review-kicker">Review item</p>
6339
+ <h3 class="quick-review-title" id="quick-review-title">Open Connect review</h3>
6340
+ </div>
6341
+ <span class="quick-review-badge" id="quick-review-badge">Review</span>
6342
+ </div>
6343
+ <p class="quick-review-meta" id="quick-review-meta">Open Connect to approve or defer this item.</p>
6344
+ <p class="quick-review-detail" id="quick-review-detail">Nothing is registered automatically.</p>
6345
+ <div class="quick-review-summary-grid">
6346
+ <div class="quick-review-summary-pill">
6347
+ <span class="quick-review-summary-label">Action</span>
6348
+ <span class="quick-review-summary-value" id="quick-review-action-summary">Approve and register</span>
6349
+ </div>
6350
+ <div class="quick-review-summary-pill">
6351
+ <span class="quick-review-summary-label">Scope</span>
6352
+ <span class="quick-review-summary-value" id="quick-review-scope-summary">Account scope</span>
6353
+ </div>
6354
+ </div>
6355
+ <div class="quick-review-status" id="quick-review-status">Waiting for review.</div>
6356
+ <div class="quick-review-scope" id="quick-review-scope" hidden>
6357
+ <div class="quick-review-scope-grid">
6358
+ <div class="quick-review-field">
6359
+ <label for="quick-review-workspace">Workspace</label>
6360
+ <select id="quick-review-workspace"></select>
6361
+ </div>
6362
+ <div class="quick-review-field">
6363
+ <label for="quick-review-project">Project</label>
6364
+ <select id="quick-review-project"></select>
6365
+ </div>
6366
+ </div>
6367
+ </div>
6368
+ <div class="quick-review-actions">
6369
+ <button class="quick-review-primary" id="quick-review-primary" type="button" disabled>Approve and register</button>
6370
+ <div class="quick-review-options-wrap">
6371
+ <button class="quick-review-options-button" id="quick-review-options" type="button" aria-expanded="false" aria-controls="quick-review-options-menu" disabled>Options ▾</button>
6372
+ <div class="quick-review-options-menu" id="quick-review-options-menu" hidden>
6373
+ <button id="quick-review-open-discovery" type="button">
6374
+ <span class="quick-review-menu-copy">
6375
+ <span class="quick-review-menu-label">Open full review</span>
6376
+ <span class="quick-review-menu-meta">See the full local discovery context.</span>
6377
+ </span>
6378
+ </button>
6379
+ <button id="quick-review-defer" type="button">
6380
+ <span class="quick-review-menu-copy">
6381
+ <span class="quick-review-menu-label">Not now (24h)</span>
6382
+ <span class="quick-review-menu-meta">Hide this prompt until tomorrow.</span>
6383
+ </span>
6384
+ </button>
6385
+ <button id="quick-review-ignore" class="danger" type="button">
6386
+ <span class="quick-review-menu-copy">
6387
+ <span class="quick-review-menu-label">Ignore locally</span>
6388
+ <span class="quick-review-menu-meta">Keep it out of this device review queue.</span>
6389
+ </span>
6390
+ </button>
6391
+ </div>
6392
+ </div>
6393
+ </div>
6394
+ </section>
6395
+ </div>
6396
+ </div>
6397
+
5821
6398
  <div class="launch-account">
5822
6399
  <button class="launch-account-btn" id="user-chip-btn" type="button" aria-expanded="false" aria-controls="account-dropdown-menu">
5823
6400
  <span class="user-chip" id="header-avatar">
@@ -6170,6 +6747,31 @@ function renderLauncherHtml(launcherToken) {
6170
6747
  <h3 class="discovery-side-title">Discovery intelligence</h3>
6171
6748
  <p class="discovery-side-copy">We automatically scan your environment and understand the systems running locally.</p>
6172
6749
 
6750
+ <section class="discovery-review-panel" id="discovery-review-panel">
6751
+ <p class="discovery-review-kicker" id="discovery-review-kicker">Review item</p>
6752
+ <h4 class="discovery-review-title" id="discovery-review-title">Select a discovery item</h4>
6753
+ <p class="discovery-review-meta" id="discovery-review-meta">Choose a model, agent, or runtime to review it here.</p>
6754
+ <p class="discovery-review-detail" id="discovery-review-detail">Registering creates or updates a Forkit Passport. Nothing is published automatically.</p>
6755
+ <div class="discovery-review-status" id="discovery-review-status">Select an item to continue.</div>
6756
+ <div class="discovery-review-scope" id="discovery-review-scope" hidden>
6757
+ <div class="discovery-review-scope-grid">
6758
+ <div class="discovery-review-field">
6759
+ <label for="discovery-review-workspace">Workspace</label>
6760
+ <select id="discovery-review-workspace"></select>
6761
+ </div>
6762
+ <div class="discovery-review-field">
6763
+ <label for="discovery-review-project">Project</label>
6764
+ <select id="discovery-review-project"></select>
6765
+ </div>
6766
+ </div>
6767
+ </div>
6768
+ <div class="discovery-review-actions">
6769
+ <button class="primary" id="discovery-review-primary" type="button" disabled>Review</button>
6770
+ <button class="secondary" id="discovery-review-defer" type="button" disabled>Defer 24h</button>
6771
+ <button class="danger" id="discovery-review-ignore" type="button" disabled>Ignore</button>
6772
+ </div>
6773
+ </section>
6774
+
6173
6775
  <ul class="discovery-note-list">
6174
6776
  <li><span class="discovery-note-label"><span class="discovery-note-icon ok">✓</span>Local detection</span><span class="discovery-note-value" id="disc-check-local">-</span></li>
6175
6777
  <li><span class="discovery-note-label"><span class="discovery-note-icon ok">✓</span>Metadata extraction</span><span class="discovery-note-value" id="disc-check-meta">-</span></li>
@@ -6611,16 +7213,21 @@ function renderLauncherHtml(launcherToken) {
6611
7213
  let settingsTab = 'profile';
6612
7214
  let currentView = 'command';
6613
7215
  let initialRouteApplied = false;
7216
+ let initialRouteFocusApplied = false;
6614
7217
  const ALL_SCOPE_VALUE = '__all__';
6615
7218
  let selectedWorkspaceScope = ALL_SCOPE_VALUE;
6616
7219
  let selectedProjectScope = ALL_SCOPE_VALUE;
6617
7220
  let scopeAccessSnapshot = null;
7221
+ let reviewScopeCacheKey = null;
7222
+ let quickReviewScopeCacheKey = null;
6618
7223
 
6619
7224
  function setText(id, value, className) {
6620
7225
  const element = document.getElementById(id);
6621
7226
  if (!element) return;
6622
7227
  element.textContent = value;
6623
- element.className = className ? className : '';
7228
+ if (className !== undefined) {
7229
+ element.className = className ? className : '';
7230
+ }
6624
7231
  }
6625
7232
 
6626
7233
  function setActivityMessage(message, tone) {
@@ -6651,6 +7258,14 @@ function renderLauncherHtml(launcherToken) {
6651
7258
  window.history.replaceState({}, '', nextUrl);
6652
7259
  }
6653
7260
 
7261
+ function readSelectedOptionLabel(select) {
7262
+ if (!select) return '';
7263
+ const option = select.options && select.selectedIndex >= 0
7264
+ ? select.options[select.selectedIndex]
7265
+ : null;
7266
+ return option ? String(option.textContent || '').trim() : '';
7267
+ }
7268
+
6654
7269
  function focusPassportByGaid(passportGaid, options) {
6655
7270
  const gaid = String(passportGaid || '').trim();
6656
7271
  if (!gaid) return false;
@@ -6683,6 +7298,562 @@ function renderLauncherHtml(launcherToken) {
6683
7298
  return false;
6684
7299
  }
6685
7300
 
7301
+ function getSelectedDiscoveryItem(itemsOverride) {
7302
+ const items = Array.isArray(itemsOverride)
7303
+ ? itemsOverride
7304
+ : latestDiscovery && Array.isArray(latestDiscovery.items)
7305
+ ? latestDiscovery.items
7306
+ : [];
7307
+ if (!items.length) return null;
7308
+ if (selectedDiscoveryId) {
7309
+ const selected = items.find((entry) => entry.id === selectedDiscoveryId);
7310
+ if (selected) return selected;
7311
+ }
7312
+ selectedDiscoveryId = items[0].id;
7313
+ return items[0];
7314
+ }
7315
+
7316
+ function setDiscoveryReviewStatus(message, tone) {
7317
+ const status = document.getElementById('discovery-review-status');
7318
+ if (!status) return;
7319
+ status.textContent = message || '';
7320
+ status.className = 'discovery-review-status' + (tone ? ' ' + tone : '');
7321
+ }
7322
+
7323
+ function canReviewRegister(item) {
7324
+ return Boolean(item && (item.kind === 'model' || item.kind === 'agent') && item.inboxGroup !== 'ignored');
7325
+ }
7326
+
7327
+ function canReviewDefer(item) {
7328
+ return Boolean(item && (item.kind === 'model' || item.kind === 'runtime') && item.inboxGroup !== 'ignored');
7329
+ }
7330
+
7331
+ function canReviewIgnore(item) {
7332
+ return Boolean(item && (item.kind === 'model' || item.kind === 'runtime') && item.inboxGroup !== 'ignored');
7333
+ }
7334
+
7335
+ function getQuickReviewItems() {
7336
+ const items = latestDiscovery && Array.isArray(latestDiscovery.items) ? latestDiscovery.items : [];
7337
+ return items.filter((item) => item && item.inboxGroup !== 'ignored' && (item.kind === 'model' || item.kind === 'agent' || item.kind === 'runtime'));
7338
+ }
7339
+
7340
+ function getQuickReviewItem() {
7341
+ const items = getQuickReviewItems();
7342
+ if (selectedDiscoveryId) {
7343
+ const selected = items.find((item) => item.id === selectedDiscoveryId);
7344
+ if (selected) return selected;
7345
+ }
7346
+ return items.find((item) => item.statusTone === 'error')
7347
+ || items.find((item) => item.inboxGroup === 'needs_confirmation')
7348
+ || items.find((item) => item.statusTone === 'warn')
7349
+ || items[0]
7350
+ || null;
7351
+ }
7352
+
7353
+ function setQuickReviewStatus(message, tone) {
7354
+ const status = document.getElementById('quick-review-status');
7355
+ if (!status) return;
7356
+ status.textContent = message || '';
7357
+ status.className = 'quick-review-status' + (tone ? ' ' + tone : '');
7358
+ }
7359
+
7360
+ function setQuickReviewOptionsOpen(open) {
7361
+ const button = document.getElementById('quick-review-options');
7362
+ const menu = document.getElementById('quick-review-options-menu');
7363
+ if (!button || !menu) return;
7364
+ menu.hidden = !open;
7365
+ button.setAttribute('aria-expanded', open ? 'true' : 'false');
7366
+ }
7367
+
7368
+ function setQuickReviewPanelOpen(open) {
7369
+ const toggle = document.getElementById('quick-review-toggle');
7370
+ const panel = document.getElementById('quick-review-panel');
7371
+ if (!toggle || !panel) return;
7372
+ panel.hidden = !open;
7373
+ toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
7374
+ if (currentView === 'command') {
7375
+ const item = getQuickReviewItem();
7376
+ updateRoute('command', open
7377
+ ? {
7378
+ focus: 'review',
7379
+ ...(item ? { item: item.id } : {}),
7380
+ }
7381
+ : {});
7382
+ }
7383
+ if (!open) {
7384
+ setQuickReviewOptionsOpen(false);
7385
+ }
7386
+ }
7387
+
7388
+ function updateQuickReviewScopeSummary(governedMode) {
7389
+ const workspaceSelect = document.getElementById('quick-review-workspace');
7390
+ const projectSelect = document.getElementById('quick-review-project');
7391
+ const workspaceId = workspaceSelect ? String(workspaceSelect.value || '').trim() : '';
7392
+ const projectId = projectSelect ? String(projectSelect.value || '').trim() : '';
7393
+ const workspaceLabel = readSelectedOptionLabel(workspaceSelect);
7394
+ const projectLabel = readSelectedOptionLabel(projectSelect);
7395
+ if (!workspaceId) {
7396
+ setText('quick-review-scope-summary', governedMode ? 'Choose governed scope' : 'Account scope');
7397
+ return;
7398
+ }
7399
+ if (!projectId) {
7400
+ setText('quick-review-scope-summary', workspaceLabel ? (workspaceLabel + ' · choose project') : 'Choose project');
7401
+ return;
7402
+ }
7403
+ setText('quick-review-scope-summary', [workspaceLabel, projectLabel].filter(Boolean).join(' · ') || 'Governed project');
7404
+ }
7405
+
7406
+ async function loadQuickReviewProjects(workspaceId, selectedProjectId, governedMode) {
7407
+ const projectSelect = document.getElementById('quick-review-project');
7408
+ const primaryButton = document.getElementById('quick-review-primary');
7409
+ if (!projectSelect) return;
7410
+ if (!workspaceId) {
7411
+ setScopeOptions(projectSelect, [{ id: '', label: governedMode ? 'Choose workspace first' : 'Account scope / no project' }], '');
7412
+ if (primaryButton) primaryButton.disabled = governedMode;
7413
+ updateQuickReviewScopeSummary(governedMode);
7414
+ return;
7415
+ }
7416
+ setScopeOptions(projectSelect, [{ id: '', label: 'Loading projects...' }], '');
7417
+ try {
7418
+ const response = await apiFetch('/api/scope/projects?workspaceId=' + encodeURIComponent(workspaceId));
7419
+ const payload = await response.json();
7420
+ const projects = payload.ok && Array.isArray(payload.projects) ? payload.projects : [];
7421
+ const options = projects.length
7422
+ ? projects.map((project) => ({ id: project.id, label: project.name || project.id }))
7423
+ : [{ id: '', label: 'No projects found' }];
7424
+ setScopeOptions(projectSelect, options, selectedProjectId || (options[0] && options[0].id) || '');
7425
+ if (!payload.ok) {
7426
+ setQuickReviewStatus(payload.message || 'Project list is unavailable right now.', 'warn');
7427
+ } else if (!projects.length) {
7428
+ setQuickReviewStatus('Create a project in this workspace before registering here.', 'warn');
7429
+ }
7430
+ if (primaryButton) {
7431
+ primaryButton.disabled = !projects.length;
7432
+ }
7433
+ updateQuickReviewScopeSummary(governedMode);
7434
+ } catch {
7435
+ setScopeOptions(projectSelect, [{ id: '', label: 'Project list unavailable' }], '');
7436
+ setQuickReviewStatus('Project list is unavailable right now.', 'warn');
7437
+ if (primaryButton) {
7438
+ primaryButton.disabled = true;
7439
+ }
7440
+ updateQuickReviewScopeSummary(governedMode);
7441
+ }
7442
+ }
7443
+
7444
+ async function renderQuickReviewNotice() {
7445
+ const anchor = document.getElementById('quick-review-anchor');
7446
+ const item = getQuickReviewItem();
7447
+ const total = getQuickReviewItems().length;
7448
+ const scopeWrap = document.getElementById('quick-review-scope');
7449
+ const workspaceSelect = document.getElementById('quick-review-workspace');
7450
+ const projectSelect = document.getElementById('quick-review-project');
7451
+ const primaryButton = document.getElementById('quick-review-primary');
7452
+ const optionsButton = document.getElementById('quick-review-options');
7453
+ const openDiscoveryButton = document.getElementById('quick-review-open-discovery');
7454
+ const deferButton = document.getElementById('quick-review-defer');
7455
+ const ignoreButton = document.getElementById('quick-review-ignore');
7456
+
7457
+ if (!anchor) return;
7458
+ if (!item || !total) {
7459
+ anchor.hidden = true;
7460
+ setQuickReviewPanelOpen(false);
7461
+ quickReviewScopeCacheKey = null;
7462
+ return;
7463
+ }
7464
+
7465
+ anchor.hidden = false;
7466
+ setText('quick-review-count', String(total));
7467
+ setText('quick-review-chip-title', item.name);
7468
+ setText('quick-review-chip-meta', total > 1 ? (String(total) + ' items need review') : (item.statusMeta || item.statusLabel));
7469
+ setText('quick-review-kicker', item.typeLabel || item.kind);
7470
+ setText('quick-review-title', item.name);
7471
+ setText('quick-review-badge', item.kind === 'runtime' ? 'Runtime' : item.kind === 'agent' ? 'Agent' : 'Model');
7472
+ setText('quick-review-meta', item.subtitle + ' · ' + item.source);
7473
+ setText('quick-review-detail', item.detailSummary || item.statusMeta || 'Review local metadata, then decide what happens next.');
7474
+ setQuickReviewStatus(item.statusLabel + (item.statusMeta ? ' · ' + item.statusMeta : ''), item.statusTone === 'muted' ? '' : item.statusTone);
7475
+
7476
+ const opensExisting = item.kind === 'runtime' || item.actionLabel === 'Open' || item.actionLabel === 'Review';
7477
+ const primaryLabel = item.kind === 'runtime'
7478
+ ? 'Open runtime review'
7479
+ : opensExisting
7480
+ ? (item.passportGaid || item.matchedPassportGaid ? 'Open passport' : 'Review in context')
7481
+ : 'Approve & register';
7482
+ setText(
7483
+ 'quick-review-action-summary',
7484
+ item.kind === 'runtime'
7485
+ ? 'Open runtime lane'
7486
+ : opensExisting
7487
+ ? (item.passportGaid || item.matchedPassportGaid ? 'Open linked passport' : 'Open full review')
7488
+ : 'Approve and register here',
7489
+ );
7490
+ if (primaryButton) {
7491
+ primaryButton.textContent = primaryLabel;
7492
+ primaryButton.disabled = item.inboxGroup === 'ignored';
7493
+ }
7494
+ if (optionsButton) {
7495
+ optionsButton.disabled = false;
7496
+ }
7497
+ if (openDiscoveryButton) {
7498
+ openDiscoveryButton.hidden = false;
7499
+ }
7500
+ if (deferButton) {
7501
+ deferButton.hidden = !canReviewDefer(item);
7502
+ }
7503
+ if (ignoreButton) {
7504
+ ignoreButton.hidden = !canReviewIgnore(item);
7505
+ }
7506
+
7507
+ const needsScope = canReviewRegister(item);
7508
+ if (!needsScope) {
7509
+ if (scopeWrap) scopeWrap.hidden = true;
7510
+ quickReviewScopeCacheKey = null;
7511
+ setText('quick-review-scope-summary', item.kind === 'runtime' ? 'Open runtime review' : 'Full review available');
7512
+ return;
7513
+ }
7514
+
7515
+ if (scopeWrap) scopeWrap.hidden = false;
7516
+ if (!workspaceSelect || !projectSelect) return;
7517
+ const cacheKey = item.id + ':' + String(item.workspaceId || '') + ':' + String(item.projectId || '');
7518
+ if (quickReviewScopeCacheKey === cacheKey) {
7519
+ return;
7520
+ }
7521
+ quickReviewScopeCacheKey = cacheKey;
7522
+ setQuickReviewStatus('Loading available workspaces…', 'warn');
7523
+ setScopeOptions(workspaceSelect, [{ id: '', label: 'Loading workspaces...' }], '');
7524
+ setScopeOptions(projectSelect, [{ id: '', label: 'Loading projects...' }], '');
7525
+
7526
+ try {
7527
+ const response = await apiFetch('/api/scope/access');
7528
+ const payload = await response.json();
7529
+ scopeAccessSnapshot = payload;
7530
+ const governedMode = payload && payload.operatingMode === 'governed';
7531
+ const workspaces = payload.ok && Array.isArray(payload.workspaces) ? payload.workspaces : [];
7532
+ const options = [
7533
+ ...(governedMode ? [] : [{ id: '', label: 'Account scope / no workspace' }]),
7534
+ ...workspaces.map((workspace) => ({ id: workspace.id, label: workspace.name || workspace.id })),
7535
+ ];
7536
+ if (governedMode && !options.length) {
7537
+ options.push({ id: '', label: 'No governed workspaces yet' });
7538
+ }
7539
+ const selectedWorkspace = item.workspaceId || payload.currentWorkspaceId || (options[0] && options[0].id) || '';
7540
+ setScopeOptions(workspaceSelect, options, selectedWorkspace);
7541
+ await loadQuickReviewProjects(selectedWorkspace, item.projectId || payload.currentProjectId || '', governedMode);
7542
+ setQuickReviewStatus(
7543
+ payload.ok
7544
+ ? (governedMode
7545
+ ? 'Choose the governed workspace and project for this registration.'
7546
+ : 'Account scope stays available. Choose governed scope only when needed.')
7547
+ : (payload.message || 'Workspace access is unavailable right now.'),
7548
+ payload.ok ? '' : 'warn',
7549
+ );
7550
+ updateQuickReviewScopeSummary(governedMode);
7551
+ } catch {
7552
+ setScopeOptions(workspaceSelect, [{ id: '', label: 'Workspace list unavailable' }], '');
7553
+ setScopeOptions(projectSelect, [{ id: '', label: 'Project list unavailable' }], '');
7554
+ setQuickReviewStatus('Workspace access is unavailable right now.', 'warn');
7555
+ if (primaryButton) {
7556
+ primaryButton.disabled = true;
7557
+ }
7558
+ setText('quick-review-scope-summary', 'Scope unavailable');
7559
+ }
7560
+ }
7561
+
7562
+ async function submitQuickReviewPrimary() {
7563
+ const item = getQuickReviewItem();
7564
+ if (!item) return;
7565
+ selectedDiscoveryId = item.id;
7566
+ if (item.kind === 'runtime' || item.actionLabel === 'Open' || item.actionLabel === 'Review') {
7567
+ await openDiscoveryContext(item);
7568
+ setQuickReviewPanelOpen(false);
7569
+ return;
7570
+ }
7571
+ if (!canReviewRegister(item)) {
7572
+ setQuickReviewStatus('Registration is not available for this item.', 'warn');
7573
+ return;
7574
+ }
7575
+ const workspaceSelect = document.getElementById('quick-review-workspace');
7576
+ const projectSelect = document.getElementById('quick-review-project');
7577
+ const primaryButton = document.getElementById('quick-review-primary');
7578
+ const workspaceId = workspaceSelect ? String(workspaceSelect.value || '').trim() : '';
7579
+ const projectId = projectSelect ? String(projectSelect.value || '').trim() : '';
7580
+ const governedMode = scopeAccessSnapshot && scopeAccessSnapshot.operatingMode === 'governed';
7581
+ if (workspaceId && !projectId) {
7582
+ setQuickReviewStatus('Select a project for the chosen workspace, or switch back to account scope.', 'warn');
7583
+ return;
7584
+ }
7585
+ if (!workspaceId && governedMode) {
7586
+ setQuickReviewStatus('Choose the governed workspace and project before registering here.', 'warn');
7587
+ return;
7588
+ }
7589
+ if (primaryButton) primaryButton.disabled = true;
7590
+ const endpoint = item.kind === 'agent' ? '/api/discovery/connect-agent' : '/api/discovery/connect-model';
7591
+ const result = await postAction(endpoint, 'Registering with Forkit Connect...', {
7592
+ method: 'POST',
7593
+ body: JSON.stringify({ selector: item.selector, workspaceId, projectId }),
7594
+ });
7595
+ if (primaryButton) primaryButton.disabled = false;
7596
+ if (!result.ok) {
7597
+ setQuickReviewStatus(result.message || 'Registration failed.', 'warn');
7598
+ setActivityMessage(result.message || 'Registration failed.', 'warn');
7599
+ return;
7600
+ }
7601
+ setQuickReviewStatus(result.message || 'Registration updated.', 'ok');
7602
+ setActivityMessage(result.message || 'Registration updated.', 'ok');
7603
+ await refreshAll();
7604
+ setQuickReviewPanelOpen(false);
7605
+ if (result.gaid || result.passportGaid) {
7606
+ await openDiscoveryContext(item);
7607
+ }
7608
+ }
7609
+
7610
+ async function submitQuickReviewDefer() {
7611
+ const item = getQuickReviewItem();
7612
+ if (!item || !canReviewDefer(item)) {
7613
+ setQuickReviewStatus('Not now is not available for this item.', 'warn');
7614
+ return;
7615
+ }
7616
+ const result = await postAction('/api/discovery/review/defer', 'Deferring this review...', {
7617
+ method: 'POST',
7618
+ body: JSON.stringify({ kind: item.kind, selector: item.selector, hours: 24 }),
7619
+ });
7620
+ setQuickReviewStatus(result.message || 'Review deferred.', result.ok ? 'ok' : 'warn');
7621
+ setActivityMessage(result.message || 'Review deferred.', result.ok ? 'ok' : 'warn');
7622
+ await refreshAll();
7623
+ setQuickReviewOptionsOpen(false);
7624
+ }
7625
+
7626
+ async function submitQuickReviewIgnore() {
7627
+ const item = getQuickReviewItem();
7628
+ if (!item || !canReviewIgnore(item)) {
7629
+ setQuickReviewStatus('Ignore is not available for this item.', 'warn');
7630
+ return;
7631
+ }
7632
+ const result = await postAction('/api/discovery/review/ignore', 'Ignoring this review item...', {
7633
+ method: 'POST',
7634
+ body: JSON.stringify({ kind: item.kind, selector: item.selector }),
7635
+ });
7636
+ setQuickReviewStatus(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
7637
+ setActivityMessage(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
7638
+ await refreshAll();
7639
+ setQuickReviewOptionsOpen(false);
7640
+ }
7641
+
7642
+ function setDiscoveryReviewButtons(config) {
7643
+ const primary = document.getElementById('discovery-review-primary');
7644
+ const defer = document.getElementById('discovery-review-defer');
7645
+ const ignore = document.getElementById('discovery-review-ignore');
7646
+ if (primary) {
7647
+ primary.textContent = config.primaryLabel || 'Review';
7648
+ primary.disabled = !config.primaryEnabled;
7649
+ }
7650
+ if (defer) {
7651
+ defer.disabled = !config.deferEnabled;
7652
+ }
7653
+ if (ignore) {
7654
+ ignore.disabled = !config.ignoreEnabled;
7655
+ }
7656
+ }
7657
+
7658
+ async function loadDiscoveryReviewProjects(workspaceId, selectedProjectId, governedMode) {
7659
+ const projectSelect = document.getElementById('discovery-review-project');
7660
+ const primaryButton = document.getElementById('discovery-review-primary');
7661
+ if (!projectSelect) return;
7662
+ if (!workspaceId) {
7663
+ setScopeOptions(projectSelect, [{ id: '', label: governedMode ? 'Choose workspace first' : 'Account scope / no project' }], '');
7664
+ if (primaryButton) primaryButton.disabled = governedMode;
7665
+ return;
7666
+ }
7667
+ setScopeOptions(projectSelect, [{ id: '', label: 'Loading projects...' }], '');
7668
+ try {
7669
+ const response = await apiFetch('/api/scope/projects?workspaceId=' + encodeURIComponent(workspaceId));
7670
+ const payload = await response.json();
7671
+ const projects = payload.ok && Array.isArray(payload.projects) ? payload.projects : [];
7672
+ const options = projects.length
7673
+ ? projects.map((project) => ({ id: project.id, label: project.name || project.id }))
7674
+ : [{ id: '', label: 'No projects found' }];
7675
+ setScopeOptions(projectSelect, options, selectedProjectId || (options[0] && options[0].id) || '');
7676
+ if (!payload.ok) {
7677
+ setDiscoveryReviewStatus(payload.message || 'Project list is unavailable right now.', 'warn');
7678
+ } else if (!projects.length) {
7679
+ setDiscoveryReviewStatus('Create a project in this workspace before registering here.', 'warn');
7680
+ }
7681
+ if (primaryButton) {
7682
+ primaryButton.disabled = !projects.length;
7683
+ }
7684
+ } catch {
7685
+ setScopeOptions(projectSelect, [{ id: '', label: 'Project list unavailable' }], '');
7686
+ setDiscoveryReviewStatus('Project list is unavailable right now.', 'warn');
7687
+ if (primaryButton) {
7688
+ primaryButton.disabled = true;
7689
+ }
7690
+ }
7691
+ }
7692
+
7693
+ async function renderDiscoveryReviewPanel() {
7694
+ const item = getSelectedDiscoveryItem();
7695
+ const scopeWrap = document.getElementById('discovery-review-scope');
7696
+ const workspaceSelect = document.getElementById('discovery-review-workspace');
7697
+ const projectSelect = document.getElementById('discovery-review-project');
7698
+
7699
+ if (!item) {
7700
+ setText('discovery-review-kicker', 'Review item');
7701
+ setText('discovery-review-title', 'Select a discovery item');
7702
+ setText('discovery-review-meta', 'Choose a model, agent, or runtime to review it here.');
7703
+ setText('discovery-review-detail', 'Registering creates or updates a Forkit Passport. Nothing is published automatically.');
7704
+ if (scopeWrap) scopeWrap.hidden = true;
7705
+ setDiscoveryReviewStatus('Select an item to continue.', '');
7706
+ setDiscoveryReviewButtons({ primaryLabel: 'Review', primaryEnabled: false, deferEnabled: false, ignoreEnabled: false });
7707
+ return;
7708
+ }
7709
+
7710
+ const typeLabel = item.typeLabel || item.kind;
7711
+ const needsScope = canReviewRegister(item);
7712
+ const opensExisting = item.actionLabel === 'Open' || item.actionLabel === 'Review';
7713
+ const primaryLabel = item.kind === 'runtime'
7714
+ ? 'Open runtime review'
7715
+ : opensExisting
7716
+ ? (item.passportGaid || item.matchedPassportGaid ? 'Open passport' : 'Review in context')
7717
+ : 'Approve and register';
7718
+
7719
+ setText('discovery-review-kicker', typeLabel);
7720
+ setText('discovery-review-title', item.name);
7721
+ setText('discovery-review-meta', item.subtitle + ' · ' + item.source);
7722
+ setText('discovery-review-detail', item.detailSummary || item.statusMeta || 'Review local metadata, then decide what happens next.');
7723
+ setDiscoveryReviewStatus(item.statusLabel + (item.statusMeta ? ' · ' + item.statusMeta : ''), item.statusTone === 'muted' ? '' : item.statusTone);
7724
+ setDiscoveryReviewButtons({
7725
+ primaryLabel,
7726
+ primaryEnabled: item.inboxGroup !== 'ignored',
7727
+ deferEnabled: canReviewDefer(item),
7728
+ ignoreEnabled: canReviewIgnore(item),
7729
+ });
7730
+
7731
+ if (!needsScope) {
7732
+ if (scopeWrap) scopeWrap.hidden = true;
7733
+ reviewScopeCacheKey = null;
7734
+ return;
7735
+ }
7736
+
7737
+ if (scopeWrap) scopeWrap.hidden = false;
7738
+ if (!workspaceSelect || !projectSelect) return;
7739
+
7740
+ const cacheKey = item.id + ':' + String(item.workspaceId || '') + ':' + String(item.projectId || '');
7741
+ if (reviewScopeCacheKey === cacheKey) {
7742
+ return;
7743
+ }
7744
+ reviewScopeCacheKey = cacheKey;
7745
+
7746
+ setDiscoveryReviewStatus('Loading available workspaces…', 'warn');
7747
+ setScopeOptions(workspaceSelect, [{ id: '', label: 'Loading workspaces...' }], '');
7748
+ setScopeOptions(projectSelect, [{ id: '', label: 'Loading projects...' }], '');
7749
+
7750
+ try {
7751
+ const response = await apiFetch('/api/scope/access');
7752
+ const payload = await response.json();
7753
+ scopeAccessSnapshot = payload;
7754
+ const governedMode = payload && payload.operatingMode === 'governed';
7755
+ const workspaces = payload.ok && Array.isArray(payload.workspaces) ? payload.workspaces : [];
7756
+ const options = [
7757
+ ...(governedMode ? [] : [{ id: '', label: 'Account scope / no workspace' }]),
7758
+ ...workspaces.map((workspace) => ({ id: workspace.id, label: workspace.name || workspace.id })),
7759
+ ];
7760
+ if (governedMode && !options.length) {
7761
+ options.push({ id: '', label: 'No governed workspaces yet' });
7762
+ }
7763
+ const selectedWorkspace = item.workspaceId || payload.currentWorkspaceId || (options[0] && options[0].id) || '';
7764
+ setScopeOptions(workspaceSelect, options, selectedWorkspace);
7765
+ await loadDiscoveryReviewProjects(selectedWorkspace, item.projectId || payload.currentProjectId || '', governedMode);
7766
+ setDiscoveryReviewStatus(
7767
+ payload.ok
7768
+ ? (governedMode
7769
+ ? 'Choose the governed workspace and project for this registration.'
7770
+ : 'Account scope stays available. Choose governed scope only when needed.')
7771
+ : (payload.message || 'Workspace access is unavailable right now.'),
7772
+ payload.ok ? '' : 'warn',
7773
+ );
7774
+ } catch {
7775
+ setScopeOptions(workspaceSelect, [{ id: '', label: 'Workspace list unavailable' }], '');
7776
+ setScopeOptions(projectSelect, [{ id: '', label: 'Project list unavailable' }], '');
7777
+ setDiscoveryReviewStatus('Workspace access is unavailable right now.', 'warn');
7778
+ setDiscoveryReviewButtons({
7779
+ primaryLabel,
7780
+ primaryEnabled: false,
7781
+ deferEnabled: canReviewDefer(item),
7782
+ ignoreEnabled: canReviewIgnore(item),
7783
+ });
7784
+ }
7785
+ }
7786
+
7787
+ async function submitDiscoveryReviewPrimary() {
7788
+ const item = getSelectedDiscoveryItem();
7789
+ if (!item) return;
7790
+ if (item.kind === 'runtime' || item.actionLabel === 'Open' || item.actionLabel === 'Review') {
7791
+ await openDiscoveryContext(item);
7792
+ return;
7793
+ }
7794
+ if (!canReviewRegister(item)) {
7795
+ setDiscoveryReviewStatus('Registration is not available for this item.', 'warn');
7796
+ return;
7797
+ }
7798
+ const workspaceSelect = document.getElementById('discovery-review-workspace');
7799
+ const projectSelect = document.getElementById('discovery-review-project');
7800
+ const primaryButton = document.getElementById('discovery-review-primary');
7801
+ const workspaceId = workspaceSelect ? String(workspaceSelect.value || '').trim() : '';
7802
+ const projectId = projectSelect ? String(projectSelect.value || '').trim() : '';
7803
+ if (workspaceId && !projectId) {
7804
+ setDiscoveryReviewStatus('Select a project for the chosen workspace, or switch back to account scope.', 'warn');
7805
+ return;
7806
+ }
7807
+ if (primaryButton) primaryButton.disabled = true;
7808
+ const endpoint = item.kind === 'agent' ? '/api/discovery/connect-agent' : '/api/discovery/connect-model';
7809
+ const result = await postAction(endpoint, 'Registering with Forkit Connect...', {
7810
+ method: 'POST',
7811
+ body: JSON.stringify({ selector: item.selector, workspaceId, projectId }),
7812
+ });
7813
+ if (primaryButton) primaryButton.disabled = false;
7814
+ if (!result.ok) {
7815
+ setDiscoveryReviewStatus(result.message || 'Registration failed.', 'warn');
7816
+ setActivityMessage(result.message || 'Registration failed.', 'warn');
7817
+ return;
7818
+ }
7819
+ setDiscoveryReviewStatus(result.message || 'Registration updated.', 'ok');
7820
+ setActivityMessage(result.message || 'Registration updated.', 'ok');
7821
+ await refreshAll();
7822
+ if (result.gaid || result.passportGaid) {
7823
+ await openDiscoveryContext(item);
7824
+ }
7825
+ }
7826
+
7827
+ async function submitDiscoveryReviewDefer() {
7828
+ const item = getSelectedDiscoveryItem();
7829
+ if (!item || !canReviewDefer(item)) {
7830
+ setDiscoveryReviewStatus('Defer is not available for this item.', 'warn');
7831
+ return;
7832
+ }
7833
+ const result = await postAction('/api/discovery/review/defer', 'Deferring this review...', {
7834
+ method: 'POST',
7835
+ body: JSON.stringify({ kind: item.kind, selector: item.selector, hours: 24 }),
7836
+ });
7837
+ setDiscoveryReviewStatus(result.message || 'Review deferred.', result.ok ? 'ok' : 'warn');
7838
+ setActivityMessage(result.message || 'Review deferred.', result.ok ? 'ok' : 'warn');
7839
+ await refreshAll();
7840
+ }
7841
+
7842
+ async function submitDiscoveryReviewIgnore() {
7843
+ const item = getSelectedDiscoveryItem();
7844
+ if (!item || !canReviewIgnore(item)) {
7845
+ setDiscoveryReviewStatus('Ignore is not available for this item.', 'warn');
7846
+ return;
7847
+ }
7848
+ const result = await postAction('/api/discovery/review/ignore', 'Ignoring this review item...', {
7849
+ method: 'POST',
7850
+ body: JSON.stringify({ kind: item.kind, selector: item.selector }),
7851
+ });
7852
+ setDiscoveryReviewStatus(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
7853
+ setActivityMessage(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
7854
+ await refreshAll();
7855
+ }
7856
+
6686
7857
  function setProfileAvatar(rootId, imageId, initialsId, initials, avatarUrl) {
6687
7858
  const root = document.getElementById(rootId);
6688
7859
  const image = document.getElementById(imageId);
@@ -7067,9 +8238,13 @@ function renderLauncherHtml(launcherToken) {
7067
8238
  setMetricValue('cmd-health-sync', formatRelativeTime(summary && summary.heartbeat && summary.heartbeat.lastSentAt), heartbeatCanSend ? 'ok' : 'warn');
7068
8239
 
7069
8240
  const boundCount = scopedPassports.filter((item) => String(item.bindingStatus || '').toLowerCase() === 'bound').length;
7070
- const subtitle = 'Scope: ' + workspaceLabel + ' / ' + projectLabel + ' · ' + boundCount + ' bound registrations.';
8241
+ const subtitle = boundCount > 0
8242
+ ? boundCount === 1
8243
+ ? '1 registration is already linked to the current scope.'
8244
+ : boundCount + ' registrations are already linked to the current scope.'
8245
+ : 'Current scope is ready for new registrations.';
7071
8246
  if (currentView === 'command') {
7072
- setText('activity-message', subtitle);
8247
+ setText('activity-message', subtitle, '');
7073
8248
  }
7074
8249
 
7075
8250
  const accountName = toDisplayName(workspaceLabel, 'Operator');
@@ -7710,6 +8885,33 @@ function renderLauncherHtml(launcherToken) {
7710
8885
  }
7711
8886
  }
7712
8887
 
8888
+ async function applyRouteFocusIfNeeded() {
8889
+ if (initialRouteFocusApplied) return;
8890
+ const route = resolveRouteState();
8891
+ if (route.view === 'command' && route.focus === 'review') {
8892
+ const reviewItems = getQuickReviewItems();
8893
+ if (!reviewItems.length) return;
8894
+ setView('command', { skipRouteUpdate: true });
8895
+ await renderQuickReviewNotice();
8896
+ setQuickReviewPanelOpen(true);
8897
+ setActivityMessage('Review the next item directly in Forkit Connect.', '');
8898
+ initialRouteFocusApplied = true;
8899
+ return;
8900
+ }
8901
+ if (route.view === 'discovery' && route.focus === 'item') {
8902
+ setView('discovery', { skipRouteUpdate: true });
8903
+ renderDiscoveryRows();
8904
+ initialRouteFocusApplied = true;
8905
+ return;
8906
+ }
8907
+ if (route.view === 'runtime' && route.focus === 'c2') {
8908
+ setView('runtime', { skipRouteUpdate: true });
8909
+ initialRouteFocusApplied = true;
8910
+ return;
8911
+ }
8912
+ initialRouteFocusApplied = true;
8913
+ }
8914
+
7713
8915
  function renderDiscoveryRows() {
7714
8916
  const rowsHost = document.getElementById('discovery-rows');
7715
8917
  const empty = document.getElementById('discovery-empty');
@@ -7729,6 +8931,13 @@ function renderLauncherHtml(launcherToken) {
7729
8931
 
7730
8932
  const visible = filtered;
7731
8933
 
8934
+ if (selectedDiscoveryId && !visible.some((item) => item.id === selectedDiscoveryId)) {
8935
+ selectedDiscoveryId = visible.length ? visible[0].id : null;
8936
+ reviewScopeCacheKey = null;
8937
+ } else if (!selectedDiscoveryId && visible.length) {
8938
+ selectedDiscoveryId = visible[0].id;
8939
+ }
8940
+
7732
8941
  rowsHost.innerHTML = visible.map((item) => '<div class="discovery-item-row' + (item.id === selectedDiscoveryId ? ' active' : '') + '" data-discovery-id="' + escapeHtml(item.id) + '">'
7733
8942
  + '<div><button class="discovery-check" type="button" aria-label="Select ' + escapeHtml(item.name) + '"></button></div>'
7734
8943
  + '<div class="discovery-item-main">'
@@ -7744,22 +8953,48 @@ function renderLauncherHtml(launcherToken) {
7744
8953
  + '<div class="discovery-action-group"><button class="discovery-action-btn" data-discovery-action="primary" data-tone="' + escapeHtml(item.actionTone || 'primary') + '" type="button">' + escapeHtml(item.actionLabel) + '</button><button class="discovery-more-btn" data-discovery-action="inspect" type="button" aria-label="More actions for ' + escapeHtml(item.name) + '">…</button></div>'
7745
8954
  + '</div>').join('');
7746
8955
 
8956
+ rowsHost.querySelectorAll('.discovery-item-row').forEach((row) => {
8957
+ row.addEventListener('click', async (event) => {
8958
+ const target = event.target;
8959
+ if (target && (target.closest('[data-discovery-action]') || target.closest('.discovery-check'))) {
8960
+ return;
8961
+ }
8962
+ selectedDiscoveryId = row.getAttribute('data-discovery-id');
8963
+ reviewScopeCacheKey = null;
8964
+ renderDiscoveryRows();
8965
+ });
8966
+ });
8967
+ rowsHost.querySelectorAll('.discovery-check').forEach((button) => {
8968
+ button.addEventListener('click', (event) => {
8969
+ event.stopPropagation();
8970
+ const row = button.closest('.discovery-item-row');
8971
+ if (!row) return;
8972
+ selectedDiscoveryId = row.getAttribute('data-discovery-id');
8973
+ reviewScopeCacheKey = null;
8974
+ renderDiscoveryRows();
8975
+ });
8976
+ });
8977
+
7747
8978
  rowsHost.querySelectorAll('[data-discovery-action="primary"]').forEach((button) => {
7748
- button.addEventListener('click', async () => {
8979
+ button.addEventListener('click', async (event) => {
8980
+ event.stopPropagation();
7749
8981
  const row = button.closest('.discovery-item-row');
7750
8982
  const item = row ? visible.find((entry) => entry.id === row.getAttribute('data-discovery-id')) : null;
7751
8983
  if (!item) return;
7752
8984
  selectedDiscoveryId = item.id;
8985
+ reviewScopeCacheKey = null;
7753
8986
  await handleDiscoveryPrimaryAction(item);
7754
8987
  });
7755
8988
  });
7756
8989
  rowsHost.querySelectorAll('[data-discovery-action="inspect"]').forEach((button) => {
7757
- button.addEventListener('click', async () => {
8990
+ button.addEventListener('click', async (event) => {
8991
+ event.stopPropagation();
7758
8992
  const row = button.closest('.discovery-item-row');
7759
8993
  const item = row ? visible.find((entry) => entry.id === row.getAttribute('data-discovery-id')) : null;
7760
8994
  if (!item) return;
7761
8995
  selectedDiscoveryId = item.id;
7762
- await handleDiscoveryInspectAction(item);
8996
+ reviewScopeCacheKey = null;
8997
+ renderDiscoveryRows();
7763
8998
  });
7764
8999
  });
7765
9000
 
@@ -7768,6 +9003,8 @@ function renderLauncherHtml(launcherToken) {
7768
9003
  if (showMoreButton) {
7769
9004
  showMoreButton.hidden = true;
7770
9005
  }
9006
+ void renderDiscoveryReviewPanel();
9007
+ void renderQuickReviewNotice();
7771
9008
  }
7772
9009
 
7773
9010
  function updateKindTabs() {
@@ -7797,6 +9034,7 @@ function renderLauncherHtml(launcherToken) {
7797
9034
  setText('disc-check-passport', discovery.checks.passportReadiness, 'muted');
7798
9035
 
7799
9036
  renderDiscoveryRows();
9037
+ void renderQuickReviewNotice();
7800
9038
  renderCommandOverview();
7801
9039
  renderGlobalLaunchHeaderAndCards();
7802
9040
  return discovery;
@@ -8385,6 +9623,133 @@ function renderLauncherHtml(launcherToken) {
8385
9623
  setView('discovery');
8386
9624
  renderDiscoveryRows();
8387
9625
  });
9626
+ const quickReviewToggle = document.getElementById('quick-review-toggle');
9627
+ if (quickReviewToggle) {
9628
+ quickReviewToggle.addEventListener('click', () => {
9629
+ const panel = document.getElementById('quick-review-panel');
9630
+ const nextOpen = Boolean(panel && panel.hidden);
9631
+ setQuickReviewPanelOpen(nextOpen);
9632
+ if (nextOpen) {
9633
+ void renderQuickReviewNotice();
9634
+ }
9635
+ });
9636
+ }
9637
+ const quickReviewPrimary = document.getElementById('quick-review-primary');
9638
+ if (quickReviewPrimary) {
9639
+ quickReviewPrimary.addEventListener('click', () => {
9640
+ void submitQuickReviewPrimary();
9641
+ });
9642
+ }
9643
+ const quickReviewOptionsButton = document.getElementById('quick-review-options');
9644
+ if (quickReviewOptionsButton) {
9645
+ quickReviewOptionsButton.addEventListener('click', (event) => {
9646
+ event.stopPropagation();
9647
+ const menu = document.getElementById('quick-review-options-menu');
9648
+ setQuickReviewOptionsOpen(Boolean(menu && menu.hidden));
9649
+ });
9650
+ }
9651
+ const quickReviewOpenDiscovery = document.getElementById('quick-review-open-discovery');
9652
+ if (quickReviewOpenDiscovery) {
9653
+ quickReviewOpenDiscovery.addEventListener('click', () => {
9654
+ setQuickReviewOptionsOpen(false);
9655
+ setQuickReviewPanelOpen(false);
9656
+ setView('discovery');
9657
+ renderDiscoveryRows();
9658
+ });
9659
+ }
9660
+ const quickReviewDefer = document.getElementById('quick-review-defer');
9661
+ if (quickReviewDefer) {
9662
+ quickReviewDefer.addEventListener('click', () => {
9663
+ void submitQuickReviewDefer();
9664
+ });
9665
+ }
9666
+ const quickReviewIgnore = document.getElementById('quick-review-ignore');
9667
+ if (quickReviewIgnore) {
9668
+ quickReviewIgnore.addEventListener('click', () => {
9669
+ void submitQuickReviewIgnore();
9670
+ });
9671
+ }
9672
+ const quickReviewWorkspaceSelect = document.getElementById('quick-review-workspace');
9673
+ if (quickReviewWorkspaceSelect) {
9674
+ quickReviewWorkspaceSelect.addEventListener('change', () => {
9675
+ const selectedWorkspaceId = String(quickReviewWorkspaceSelect.value || '').trim();
9676
+ const governedMode = scopeAccessSnapshot && scopeAccessSnapshot.operatingMode === 'governed';
9677
+ quickReviewScopeCacheKey = null;
9678
+ void loadQuickReviewProjects(selectedWorkspaceId, '', governedMode);
9679
+ updateQuickReviewScopeSummary(governedMode);
9680
+ setQuickReviewStatus(
9681
+ selectedWorkspaceId
9682
+ ? 'Choose the governed project for this registration.'
9683
+ : 'Account scope stays available. Choose governed scope only when needed.',
9684
+ '',
9685
+ );
9686
+ });
9687
+ }
9688
+ const quickReviewProjectSelect = document.getElementById('quick-review-project');
9689
+ if (quickReviewProjectSelect) {
9690
+ quickReviewProjectSelect.addEventListener('change', () => {
9691
+ const primaryButton = document.getElementById('quick-review-primary');
9692
+ const selectedWorkspaceId = quickReviewWorkspaceSelect
9693
+ ? String(quickReviewWorkspaceSelect.value || '').trim()
9694
+ : '';
9695
+ const selectedProjectId = String(quickReviewProjectSelect.value || '').trim();
9696
+ const governedMode = scopeAccessSnapshot && scopeAccessSnapshot.operatingMode === 'governed';
9697
+ updateQuickReviewScopeSummary(governedMode);
9698
+ if (primaryButton) {
9699
+ primaryButton.disabled = Boolean(selectedWorkspaceId && !selectedProjectId && governedMode);
9700
+ }
9701
+ });
9702
+ }
9703
+ const discoveryReviewPrimaryBtn = document.getElementById('discovery-review-primary');
9704
+ if (discoveryReviewPrimaryBtn) {
9705
+ discoveryReviewPrimaryBtn.addEventListener('click', () => {
9706
+ void submitDiscoveryReviewPrimary();
9707
+ });
9708
+ }
9709
+ const discoveryReviewDeferBtn = document.getElementById('discovery-review-defer');
9710
+ if (discoveryReviewDeferBtn) {
9711
+ discoveryReviewDeferBtn.addEventListener('click', () => {
9712
+ void submitDiscoveryReviewDefer();
9713
+ });
9714
+ }
9715
+ const discoveryReviewIgnoreBtn = document.getElementById('discovery-review-ignore');
9716
+ if (discoveryReviewIgnoreBtn) {
9717
+ discoveryReviewIgnoreBtn.addEventListener('click', () => {
9718
+ void submitDiscoveryReviewIgnore();
9719
+ });
9720
+ }
9721
+ const discoveryReviewWorkspaceSelect = document.getElementById('discovery-review-workspace');
9722
+ if (discoveryReviewWorkspaceSelect) {
9723
+ discoveryReviewWorkspaceSelect.addEventListener('change', () => {
9724
+ const selectedItem = getSelectedDiscoveryItem();
9725
+ const selectedWorkspaceId = String(discoveryReviewWorkspaceSelect.value || '').trim();
9726
+ const governedMode = scopeAccessSnapshot && scopeAccessSnapshot.operatingMode === 'governed';
9727
+ void loadDiscoveryReviewProjects(selectedWorkspaceId, '', governedMode);
9728
+ if (selectedItem && canReviewRegister(selectedItem)) {
9729
+ setDiscoveryReviewStatus(
9730
+ selectedWorkspaceId
9731
+ ? 'Choose the governed project for this registration.'
9732
+ : 'Account scope stays available. Choose governed scope only when needed.',
9733
+ '',
9734
+ );
9735
+ }
9736
+ });
9737
+ }
9738
+ const discoveryReviewProjectSelect = document.getElementById('discovery-review-project');
9739
+ if (discoveryReviewProjectSelect) {
9740
+ discoveryReviewProjectSelect.addEventListener('change', () => {
9741
+ const selectedItem = getSelectedDiscoveryItem();
9742
+ const primaryButton = document.getElementById('discovery-review-primary');
9743
+ const selectedWorkspaceId = discoveryReviewWorkspaceSelect
9744
+ ? String(discoveryReviewWorkspaceSelect.value || '').trim()
9745
+ : '';
9746
+ const selectedProjectId = String(discoveryReviewProjectSelect.value || '').trim();
9747
+ const governedMode = scopeAccessSnapshot && scopeAccessSnapshot.operatingMode === 'governed';
9748
+ if (primaryButton && selectedItem && canReviewRegister(selectedItem)) {
9749
+ primaryButton.disabled = Boolean(selectedWorkspaceId && !selectedProjectId && governedMode);
9750
+ }
9751
+ });
9752
+ }
8388
9753
  document.getElementById('runtime-send-heartbeat').addEventListener('click', handleHeartbeat);
8389
9754
  document.getElementById('diag-action-scan').addEventListener('click', handleScan);
8390
9755
  document.getElementById('diag-action-heartbeat').addEventListener('click', handleHeartbeat);
@@ -8412,6 +9777,7 @@ function renderLauncherHtml(launcherToken) {
8412
9777
 
8413
9778
  const accountDropdownBtn = document.getElementById('user-chip-btn');
8414
9779
  const accountDropdownMenu = document.getElementById('account-dropdown-menu');
9780
+ const quickReviewAnchor = document.getElementById('quick-review-anchor');
8415
9781
  if (accountDropdownBtn && accountDropdownMenu) {
8416
9782
  accountDropdownBtn.addEventListener('click', () => {
8417
9783
  const nextHidden = !accountDropdownMenu.hidden;
@@ -8424,9 +9790,11 @@ function renderLauncherHtml(launcherToken) {
8424
9790
  const target = event.target;
8425
9791
  if (!target) return;
8426
9792
  if (accountDropdownBtn.contains(target) || accountDropdownMenu.contains(target)) return;
9793
+ if (quickReviewAnchor && quickReviewAnchor.contains(target)) return;
8427
9794
  closeScopeMenus();
8428
9795
  accountDropdownMenu.hidden = true;
8429
9796
  accountDropdownBtn.setAttribute('aria-expanded', 'false');
9797
+ setQuickReviewPanelOpen(false);
8430
9798
  });
8431
9799
  }
8432
9800
 
@@ -8612,8 +9980,9 @@ function renderLauncherHtml(launcherToken) {
8612
9980
  setView('command', { skipRouteUpdate: true });
8613
9981
  applyInitialRouteIfNeeded();
8614
9982
 
8615
- refreshAll().then(() => {
9983
+ refreshAll().then(async () => {
8616
9984
  applyInitialRouteIfNeeded();
9985
+ await applyRouteFocusIfNeeded();
8617
9986
  }).catch((error) => {
8618
9987
  setText('activity-message', error instanceof Error ? error.message : 'Launcher failed to load', 'error');
8619
9988
  });
@@ -9115,6 +10484,73 @@ function createLauncherApp(options) {
9115
10484
  response.status(400).json({ ok: false, message });
9116
10485
  }
9117
10486
  });
10487
+ app.post('/api/discovery/review/defer', (request, response) => {
10488
+ try {
10489
+ const kind = typeof request.body?.kind === 'string' ? request.body.kind.trim() : '';
10490
+ const selector = typeof request.body?.selector === 'string' ? request.body.selector.trim() : '';
10491
+ const hours = typeof request.body?.hours === 'number'
10492
+ ? request.body.hours
10493
+ : Number.parseInt(String(request.body?.hours ?? '24'), 10);
10494
+ if (!selector) {
10495
+ response.status(400).json({ ok: false, message: 'Missing review selector.' });
10496
+ return;
10497
+ }
10498
+ if (kind === 'model') {
10499
+ options.service.deferDetectedModel(selector, Number.isFinite(hours) ? hours : 24);
10500
+ response.json({ ok: true, message: 'Model review deferred for 24 hours.' });
10501
+ return;
10502
+ }
10503
+ if (kind === 'runtime') {
10504
+ options.service.deferRuntimeSuggestion(selector, Number.isFinite(hours) ? hours : 24);
10505
+ response.json({ ok: true, message: 'Runtime review deferred for 24 hours.' });
10506
+ return;
10507
+ }
10508
+ if (kind === 'agent') {
10509
+ response.status(400).json({
10510
+ ok: false,
10511
+ message: 'Agent defer is not available yet in the local review surface.',
10512
+ });
10513
+ return;
10514
+ }
10515
+ response.status(400).json({ ok: false, message: 'Unsupported review kind.' });
10516
+ }
10517
+ catch (error) {
10518
+ const message = error instanceof Error ? error.message : 'discovery_review_defer_failed';
10519
+ response.status(400).json({ ok: false, message });
10520
+ }
10521
+ });
10522
+ app.post('/api/discovery/review/ignore', (request, response) => {
10523
+ try {
10524
+ const kind = typeof request.body?.kind === 'string' ? request.body.kind.trim() : '';
10525
+ const selector = typeof request.body?.selector === 'string' ? request.body.selector.trim() : '';
10526
+ if (!selector) {
10527
+ response.status(400).json({ ok: false, message: 'Missing review selector.' });
10528
+ return;
10529
+ }
10530
+ if (kind === 'model') {
10531
+ options.service.ignoreDetectedModel(selector);
10532
+ response.json({ ok: true, message: 'Model review ignored locally.' });
10533
+ return;
10534
+ }
10535
+ if (kind === 'runtime') {
10536
+ options.service.ignoreRuntimeSuggestion(selector);
10537
+ response.json({ ok: true, message: 'Runtime review ignored locally.' });
10538
+ return;
10539
+ }
10540
+ if (kind === 'agent') {
10541
+ response.status(400).json({
10542
+ ok: false,
10543
+ message: 'Agent ignore is not available yet in the local review surface.',
10544
+ });
10545
+ return;
10546
+ }
10547
+ response.status(400).json({ ok: false, message: 'Unsupported review kind.' });
10548
+ }
10549
+ catch (error) {
10550
+ const message = error instanceof Error ? error.message : 'discovery_review_ignore_failed';
10551
+ response.status(400).json({ ok: false, message });
10552
+ }
10553
+ });
9118
10554
  app.get('/api/passports', (_request, response) => {
9119
10555
  try {
9120
10556
  response.json(buildPassports(options.service));