mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
docs: redesign maturity scorecard pages (#96057)
Merged via squash.
Prepared head SHA: d2c680a48e
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Reviewed-by: @vincentkoc
This commit is contained in:
parent
67b26126ce
commit
9089a8ab32
5 changed files with 8143 additions and 1108 deletions
|
|
@ -1654,6 +1654,15 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Maturity",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Maturity docs",
|
||||
"pages": ["maturity/scorecard", "maturity/taxonomy"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Reference",
|
||||
"groups": [
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
750
docs/style.css
750
docs/style.css
|
|
@ -135,3 +135,753 @@ html.dark .nav-tabs-underline {
|
|||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.maturity-hero {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
margin: 10px 0 38px;
|
||||
padding: 4px 0 26px 20px;
|
||||
border-bottom: 1px solid color-mix(in oklab, rgb(var(--primary)) 22%, transparent);
|
||||
border-left: 3px solid rgb(var(--primary));
|
||||
}
|
||||
|
||||
.maturity-hero-compact {
|
||||
margin-bottom: 34px;
|
||||
}
|
||||
|
||||
.maturity-hero h2 {
|
||||
max-width: 46rem;
|
||||
margin: 0;
|
||||
font-size: clamp(26px, 3vw, 38px);
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.maturity-hero-title {
|
||||
max-width: 46rem;
|
||||
margin: 0;
|
||||
font-size: clamp(26px, 3vw, 38px);
|
||||
font-weight: 750;
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.maturity-hero > p:not(.maturity-kicker):not(.maturity-jump-links) {
|
||||
max-width: 58rem;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
line-height: 1.65;
|
||||
opacity: 0.76;
|
||||
}
|
||||
|
||||
.maturity-kicker {
|
||||
margin: 0;
|
||||
color: rgb(var(--primary));
|
||||
font-size: 10px;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.1em;
|
||||
line-height: 1.3;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.maturity-jump-links {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.maturity-jump-links a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.maturity-jump-links a:hover {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.maturity-score-stable,
|
||||
.maturity-band-stable {
|
||||
color: #4ca574;
|
||||
}
|
||||
|
||||
.maturity-score-beta,
|
||||
.maturity-band-beta {
|
||||
color: #849fd2;
|
||||
}
|
||||
|
||||
.maturity-score-alpha,
|
||||
.maturity-band-alpha {
|
||||
color: #d39a4b;
|
||||
}
|
||||
|
||||
.maturity-score-experimental,
|
||||
.maturity-band-experimental {
|
||||
color: #dc7669;
|
||||
}
|
||||
|
||||
.maturity-score-clawesome,
|
||||
.maturity-band-clawesome {
|
||||
color: #46b59a;
|
||||
}
|
||||
|
||||
.maturity-level-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
padding: 3px 8px;
|
||||
border: 1px solid color-mix(in oklab, currentColor 32%, transparent);
|
||||
border-radius: 999px;
|
||||
background: color-mix(in oklab, currentColor 10%, transparent);
|
||||
color: inherit;
|
||||
font-size: 10px;
|
||||
font-weight: 750;
|
||||
line-height: 1.25;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.maturity-level-code {
|
||||
font-size: 9px;
|
||||
letter-spacing: 0.04em;
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.maturity-level-experimental {
|
||||
color: #dc7669;
|
||||
}
|
||||
|
||||
.maturity-level-alpha {
|
||||
color: #d39a4b;
|
||||
}
|
||||
|
||||
.maturity-level-beta {
|
||||
color: #849fd2;
|
||||
}
|
||||
|
||||
.maturity-level-stable {
|
||||
color: #4ca574;
|
||||
}
|
||||
|
||||
.maturity-level-clawesome {
|
||||
color: #46b59a;
|
||||
}
|
||||
|
||||
.maturity-summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
margin: 14px 0 20px;
|
||||
border-top: 1px solid color-mix(in oklab, rgb(var(--primary)) 18%, transparent);
|
||||
border-bottom: 1px solid color-mix(in oklab, rgb(var(--primary)) 18%, transparent);
|
||||
}
|
||||
|
||||
.maturity-summary-item {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
padding: 18px 20px 18px 0;
|
||||
}
|
||||
|
||||
.maturity-summary-item + .maturity-summary-item {
|
||||
padding-left: 20px;
|
||||
border-left: 1px solid color-mix(in oklab, rgb(var(--primary)) 14%, transparent);
|
||||
}
|
||||
|
||||
.maturity-summary-heading {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 9px;
|
||||
}
|
||||
|
||||
.maturity-summary-value {
|
||||
display: inline-block;
|
||||
font-size: 30px;
|
||||
font-weight: 750;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.maturity-summary-heading > span:not(.maturity-summary-value) {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.maturity-summary-bar {
|
||||
height: 7px;
|
||||
overflow: hidden;
|
||||
background: color-mix(in oklab, currentColor 14%, transparent);
|
||||
}
|
||||
|
||||
.maturity-summary-bar span {
|
||||
display: block;
|
||||
width: calc(var(--score) * 1%);
|
||||
height: 100%;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.maturity-summary-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 10px;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.maturity-summary-meta span:first-child {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.maturity-summary-meta span:last-child {
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.maturity-band-list {
|
||||
display: flex;
|
||||
margin: 12px 0 30px;
|
||||
border-top: 1px solid color-mix(in oklab, rgb(var(--primary)) 16%, transparent);
|
||||
border-bottom: 1px solid color-mix(in oklab, rgb(var(--primary)) 16%, transparent);
|
||||
}
|
||||
|
||||
.maturity-band {
|
||||
display: grid;
|
||||
flex: 1 1 0;
|
||||
gap: 3px;
|
||||
padding: 10px 12px 11px 0;
|
||||
}
|
||||
|
||||
.maturity-band + .maturity-band {
|
||||
padding-left: 12px;
|
||||
border-left: 1px solid color-mix(in oklab, rgb(var(--primary)) 12%, transparent);
|
||||
}
|
||||
|
||||
.maturity-band-title {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.maturity-band-title + span {
|
||||
color: inherit;
|
||||
font-size: 11px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.maturity-band > span:last-child {
|
||||
color: inherit;
|
||||
font-size: 11px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.maturity-band span {
|
||||
color: inherit;
|
||||
font-size: 11px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.maturity-band .maturity-level-pill {
|
||||
font-size: 10px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.maturity-band .maturity-level-pill span {
|
||||
font-size: inherit;
|
||||
opacity: inherit;
|
||||
}
|
||||
|
||||
.maturity-score {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
color: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.maturity-score-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.maturity-score-label > span:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.maturity-score-label > span:last-child {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.maturity-score-label .maturity-level-pill {
|
||||
gap: 4px;
|
||||
padding: 2px 6px;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.maturity-score-label-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.maturity-summary-meta .maturity-level-pill {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.maturity-meter {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
overflow: hidden;
|
||||
background: color-mix(in oklab, currentColor 15%, transparent);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.maturity-meter > span {
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.maturity-score-unscored,
|
||||
.maturity-lts-none {
|
||||
color: inherit;
|
||||
opacity: 0.52;
|
||||
}
|
||||
|
||||
.maturity-surface-table {
|
||||
display: grid;
|
||||
gap: 0;
|
||||
margin: 8px 0 22px;
|
||||
}
|
||||
|
||||
.maturity-surface-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(190px, 1.55fr) repeat(3, minmax(110px, 1fr)) minmax(72px, 0.55fr);
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 13px 0;
|
||||
border-top: 1px solid color-mix(in oklab, currentColor 14%, transparent);
|
||||
}
|
||||
|
||||
.maturity-surface-row-header {
|
||||
padding: 0 0 9px;
|
||||
border-top: 0;
|
||||
color: inherit;
|
||||
font-size: 10px;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.04em;
|
||||
opacity: 0.56;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.maturity-surface-name {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
min-width: 0;
|
||||
border-bottom: 0 !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.maturity-surface-name:hover .maturity-surface-title {
|
||||
color: rgb(var(--primary));
|
||||
}
|
||||
|
||||
.maturity-surface-title {
|
||||
overflow-wrap: anywhere;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.maturity-surface-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.maturity-surface-meta > span:not(.maturity-level-pill) {
|
||||
font-size: 11px;
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.maturity-surface-meta .maturity-level-pill {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.maturity-surface-metric {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.maturity-surface-metric-label {
|
||||
display: none;
|
||||
font-size: 10px;
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.maturity-surface-support {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.maturity-lts {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.maturity-lts::before {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.maturity-lts-partial {
|
||||
color: #d39a4b;
|
||||
}
|
||||
|
||||
.maturity-lts-full {
|
||||
color: #4ca574;
|
||||
}
|
||||
|
||||
.maturity-evidence-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
margin: 14px 0 24px;
|
||||
border-top: 1px solid color-mix(in oklab, rgb(var(--primary)) 16%, transparent);
|
||||
border-bottom: 1px solid color-mix(in oklab, rgb(var(--primary)) 16%, transparent);
|
||||
}
|
||||
|
||||
.maturity-evidence-card {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
padding: 14px 16px 14px 0;
|
||||
}
|
||||
|
||||
.maturity-evidence-card + .maturity-evidence-card {
|
||||
padding-left: 16px;
|
||||
border-left: 1px solid color-mix(in oklab, rgb(var(--primary)) 12%, transparent);
|
||||
}
|
||||
|
||||
.maturity-evidence-title {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.maturity-evidence-card span {
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
opacity: 0.68;
|
||||
}
|
||||
|
||||
.maturity-readiness-summary {
|
||||
margin: 0 0 12px;
|
||||
font-size: 12px;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.maturity-readiness-list {
|
||||
display: grid;
|
||||
margin: 0;
|
||||
border-top: 1px solid color-mix(in oklab, rgb(var(--primary)) 14%, transparent);
|
||||
}
|
||||
|
||||
.maturity-readiness-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.7fr) minmax(120px, 0.8fr) minmax(110px, 0.7fr);
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
padding: 11px 0;
|
||||
border-bottom: 1px solid color-mix(in oklab, rgb(var(--primary)) 10%, transparent);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.maturity-readiness-row-header {
|
||||
padding: 8px 0;
|
||||
border-bottom-color: color-mix(in oklab, rgb(var(--primary)) 14%, transparent);
|
||||
font-size: 10px;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.04em;
|
||||
opacity: 0.56;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.maturity-readiness-area {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.maturity-readiness-title {
|
||||
overflow-wrap: anywhere;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.maturity-readiness-status {
|
||||
font-size: 10px;
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.maturity-readiness-status-ready {
|
||||
color: #4ca574;
|
||||
}
|
||||
|
||||
.maturity-readiness-status-partially-reviewed {
|
||||
color: #d39a4b;
|
||||
}
|
||||
|
||||
.maturity-readiness-status-needs-review {
|
||||
color: #dc7669;
|
||||
}
|
||||
|
||||
.maturity-category-list {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.maturity-category-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(180px, 1.55fr) repeat(3, minmax(100px, 1fr)) minmax(140px, 1.2fr);
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-top: 1px solid color-mix(in oklab, rgb(var(--primary)) 11%, transparent);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.maturity-category-row-header {
|
||||
padding: 8px 0;
|
||||
border-top: 0;
|
||||
color: inherit;
|
||||
font-size: 10px;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.04em;
|
||||
opacity: 0.56;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.maturity-category-area {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.maturity-category-title {
|
||||
overflow-wrap: anywhere;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.maturity-category-area > span:last-child {
|
||||
font-size: 10px;
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.maturity-category-docs {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.maturity-category-docs a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.maturity-category-docs a:hover {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.maturity-level-list {
|
||||
display: grid;
|
||||
margin: 12px 0 28px;
|
||||
border-top: 1px solid color-mix(in oklab, rgb(var(--primary)) 16%, transparent);
|
||||
border-bottom: 1px solid color-mix(in oklab, rgb(var(--primary)) 16%, transparent);
|
||||
}
|
||||
|
||||
.maturity-level-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(130px, 0.32fr) minmax(0, 1fr);
|
||||
gap: 4px 14px;
|
||||
padding: 13px 0;
|
||||
border-top: 1px solid color-mix(in oklab, rgb(var(--primary)) 11%, transparent);
|
||||
}
|
||||
|
||||
.maturity-level-row:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.maturity-level-title {
|
||||
grid-row: span 2;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.maturity-level-title .maturity-level-pill {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.maturity-level-row span,
|
||||
.maturity-level-promotion {
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
opacity: 0.68;
|
||||
}
|
||||
|
||||
.maturity-surface-link {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
margin: 0;
|
||||
padding: 11px 0;
|
||||
border-bottom: 1px solid color-mix(in oklab, currentColor 14%, transparent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.maturity-surface-link:hover {
|
||||
color: rgb(var(--primary));
|
||||
}
|
||||
|
||||
.maturity-surface-link .maturity-surface-title {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.maturity-surface-link > .maturity-surface-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.maturity-surface-link > .maturity-surface-meta > span:not(.maturity-level-pill) {
|
||||
font-size: 11px;
|
||||
opacity: 0.68;
|
||||
}
|
||||
|
||||
.maturity-surface-link > .maturity-surface-meta .maturity-level-pill {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.maturity-surface-rollup {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px 14px;
|
||||
margin: 0 0 14px;
|
||||
padding: 9px 0;
|
||||
border-top: 1px solid color-mix(in oklab, rgb(var(--primary)) 13%, transparent);
|
||||
border-bottom: 1px solid color-mix(in oklab, rgb(var(--primary)) 13%, transparent);
|
||||
}
|
||||
|
||||
.maturity-surface-rollup > span {
|
||||
color: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#content table .maturity-score,
|
||||
#content table .maturity-lts {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.maturity-summary-grid,
|
||||
.maturity-evidence-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.maturity-summary-item,
|
||||
.maturity-summary-item + .maturity-summary-item,
|
||||
.maturity-evidence-card,
|
||||
.maturity-evidence-card + .maturity-evidence-card {
|
||||
padding: 14px 0;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.maturity-summary-item + .maturity-summary-item,
|
||||
.maturity-evidence-card + .maturity-evidence-card {
|
||||
border-top: 1px solid color-mix(in oklab, rgb(var(--primary)) 12%, transparent);
|
||||
}
|
||||
|
||||
.maturity-surface-row {
|
||||
grid-template-columns: minmax(160px, 1.35fr) repeat(3, minmax(98px, 1fr)) minmax(70px, 0.5fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.maturity-category-row {
|
||||
grid-template-columns: minmax(160px, 1.35fr) repeat(3, minmax(86px, 1fr)) minmax(110px, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.maturity-hero {
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
||||
.maturity-surface-row-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.maturity-surface-row {
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 9px 12px;
|
||||
}
|
||||
|
||||
.maturity-surface-metric {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.maturity-surface-metric-label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.maturity-surface-support {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.maturity-readiness-row,
|
||||
.maturity-category-row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.maturity-readiness-row-header,
|
||||
.maturity-category-row-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.maturity-category-docs {
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.maturity-band-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.maturity-band + .maturity-band {
|
||||
padding-left: 0;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.maturity-level-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.maturity-level-title {
|
||||
grid-row: auto;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,10 +175,6 @@ function markdownEscape(value: RenderScalar): string {
|
|||
return String(value ?? "").replaceAll("|", "\\|");
|
||||
}
|
||||
|
||||
function yamlCode(value: RenderScalar): string {
|
||||
return `\`${markdownEscape(value)}\``;
|
||||
}
|
||||
|
||||
function markdownSlug(value: string): string {
|
||||
return value
|
||||
.trim()
|
||||
|
|
@ -256,31 +252,231 @@ function docsLink(docPath: string, docsRouteIndex: DocsRouteIndex): string | und
|
|||
return `[${markdownEscape(title)}](/${markdownEscape(publicHref)})`;
|
||||
}
|
||||
|
||||
function markdownTable(rows: RenderScalar[][]): string[] {
|
||||
if (rows.length === 0) {
|
||||
return [];
|
||||
function scorePercent(value?: QaMaturityScoreObject): number | undefined {
|
||||
if (!value || typeof value !== "object" || !Number.isFinite(value.score)) {
|
||||
return undefined;
|
||||
}
|
||||
const columnCount = Math.max(...rows.map((row) => row.length));
|
||||
const normalizedRows = rows.map((row) =>
|
||||
Array.from({ length: columnCount }, (_, index) => String(row[index] ?? "")),
|
||||
);
|
||||
const widths = Array.from({ length: columnCount }, (_, index) =>
|
||||
Math.max(3, ...normalizedRows.map((row) => row[index]?.length ?? 0)),
|
||||
);
|
||||
const formatRow = (row: string[]) =>
|
||||
`| ${row.map((cell, index) => cell.padEnd(widths[index] ?? 3)).join(" | ")} |`;
|
||||
return Math.max(0, Math.min(100, Math.round(value.score)));
|
||||
}
|
||||
|
||||
function scoreClass(value?: QaMaturityScoreObject): string {
|
||||
const score = scorePercent(value);
|
||||
if (score === undefined) {
|
||||
return "maturity-score-unscored";
|
||||
}
|
||||
if (score >= 95) {
|
||||
return "maturity-score-clawesome";
|
||||
}
|
||||
if (score >= 80) {
|
||||
return "maturity-score-stable";
|
||||
}
|
||||
if (score >= 70) {
|
||||
return "maturity-score-beta";
|
||||
}
|
||||
if (score >= 50) {
|
||||
return "maturity-score-alpha";
|
||||
}
|
||||
return "maturity-score-experimental";
|
||||
}
|
||||
|
||||
function scoreLabel(value?: QaMaturityScoreObject): string {
|
||||
if (!value || typeof value !== "object") {
|
||||
return "Unscored";
|
||||
}
|
||||
const label = maturityDisplayLabel(value.label ?? "Unscored");
|
||||
return `${label}${scorePercent(value) === undefined ? "" : ` - ${scorePercent(value)}%`}`;
|
||||
}
|
||||
|
||||
function scoreMeter(value?: QaMaturityScoreObject): string {
|
||||
const score = scorePercent(value);
|
||||
if (score === undefined) {
|
||||
return '<span className="maturity-score maturity-score-unscored"><span className="maturity-score-label"><span>Unscored</span><span>-</span></span></span>';
|
||||
}
|
||||
return `<span className="maturity-score ${scoreClass(value)}"><span className="maturity-score-label">${maturityLabelPill(value?.label ?? "Unscored")}<span>${score}%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "${score}%" }} /></span></span>`;
|
||||
}
|
||||
|
||||
function scoreSummary(
|
||||
title: string,
|
||||
value: QaMaturityScoreObject | undefined,
|
||||
description: string,
|
||||
): string[] {
|
||||
const score = scorePercent(value);
|
||||
const displayScore = score === undefined ? "-" : `${score}%`;
|
||||
const cssScore = score === undefined ? "0" : String(score);
|
||||
return [
|
||||
formatRow(normalizedRows[0] ?? []),
|
||||
formatRow(widths.map((width) => "-".repeat(width))),
|
||||
...normalizedRows.slice(1).map(formatRow),
|
||||
`<div className="maturity-summary-item ${scoreClass(value)}">`,
|
||||
' <div className="maturity-summary-heading">',
|
||||
` <span className="maturity-summary-value">${displayScore}</span>`,
|
||||
` <span>${markdownEscape(title)}</span>`,
|
||||
" </div>",
|
||||
` <div className="maturity-summary-bar" style={{ "--score": "${cssScore}" }}><span /></div>`,
|
||||
' <div className="maturity-summary-meta">',
|
||||
` ${maturityLabelPill(value?.label ?? "Unscored")}`,
|
||||
` <span>${markdownEscape(description)}</span>`,
|
||||
" </div>",
|
||||
"</div>",
|
||||
];
|
||||
}
|
||||
|
||||
function scoreText(value?: QaMaturityScoreObject): string {
|
||||
if (!value || typeof value !== "object") {
|
||||
return "`Unscored`";
|
||||
function maturityLtsBadge(lts?: QaMaturityScoreSurfaceLts): string {
|
||||
if (!lts || typeof lts !== "object") {
|
||||
return '<span className="maturity-lts maturity-lts-none">Unscored</span>';
|
||||
}
|
||||
return `\`${markdownEscape(value.label ?? "")} (${markdownEscape(value.score ?? "")}%)\``;
|
||||
const supportedCategories = lts.supported_categories ?? 0;
|
||||
const status = lts.status ?? "unknown";
|
||||
const label = status === "full" ? "Full" : status === "partial" ? "Partial" : "None";
|
||||
const detail = status === "none" ? "" : ` - ${supportedCategories}`;
|
||||
return `<span className="maturity-lts maturity-lts-${status}">${label}${detail}</span>`;
|
||||
}
|
||||
|
||||
function maturityLevelClass(code: RenderScalar): string {
|
||||
const level = String(code ?? "")
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
if (level === "M5") {
|
||||
return "maturity-level-clawesome";
|
||||
}
|
||||
if (level === "M4") {
|
||||
return "maturity-level-stable";
|
||||
}
|
||||
if (level === "M3") {
|
||||
return "maturity-level-beta";
|
||||
}
|
||||
if (level === "M2") {
|
||||
return "maturity-level-alpha";
|
||||
}
|
||||
return "maturity-level-experimental";
|
||||
}
|
||||
|
||||
function maturityLabelCode(label: RenderScalar): string | undefined {
|
||||
switch (
|
||||
String(label ?? "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
) {
|
||||
case "planned":
|
||||
return "M0";
|
||||
case "experimental":
|
||||
return "M1";
|
||||
case "alpha":
|
||||
return "M2";
|
||||
case "beta":
|
||||
return "M3";
|
||||
case "stable":
|
||||
return "M4";
|
||||
case "lovable":
|
||||
case "clawesome":
|
||||
return "M5";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function maturityDisplayLabel(label: RenderScalar): string {
|
||||
return String(label ?? "")
|
||||
.trim()
|
||||
.toLowerCase() === "lovable"
|
||||
? "Clawesome"
|
||||
: String(label ?? "");
|
||||
}
|
||||
|
||||
function maturityLabelPill(label: RenderScalar): string {
|
||||
const code = maturityLabelCode(label);
|
||||
if (!code) {
|
||||
return `<span className="maturity-score-label-text">${markdownEscape(maturityDisplayLabel(label))}</span>`;
|
||||
}
|
||||
return `<span className="maturity-level-pill ${maturityLevelClass(code)}">${markdownEscape(maturityDisplayLabel(label))}</span>`;
|
||||
}
|
||||
|
||||
function maturityBandClass(label: RenderScalar): string {
|
||||
const code = maturityLabelCode(label);
|
||||
return code
|
||||
? maturityLevelClass(code).replace("maturity-level-", "maturity-band-")
|
||||
: "maturity-band-experimental";
|
||||
}
|
||||
|
||||
function maturityLevelPill(code: RenderScalar, label: RenderScalar): string {
|
||||
return `<span className="maturity-level-pill ${maturityLevelClass(code)}"><span className="maturity-level-code">${markdownEscape(code)}</span><span>${markdownEscape(maturityDisplayLabel(label))}</span></span>`;
|
||||
}
|
||||
|
||||
function maturityLevelPillFromText(value: string): string {
|
||||
const match = value.trim().match(/^(M\d+)\s+(.+)$/i);
|
||||
if (!match) {
|
||||
return `<span className="maturity-level-pill maturity-level-experimental">${markdownEscape(value)}</span>`;
|
||||
}
|
||||
return maturityLevelPill(match[1], match[2]);
|
||||
}
|
||||
|
||||
function indentMarkdown(lines: string[], spaces = 4): string[] {
|
||||
const prefix = " ".repeat(spaces);
|
||||
return lines.map((line) => (line ? `${prefix}${line}` : ""));
|
||||
}
|
||||
|
||||
function renderSurfaceRows({
|
||||
coverage,
|
||||
levels,
|
||||
scoreSurfaces,
|
||||
surfaces,
|
||||
}: {
|
||||
coverage: DerivedCoverageScores;
|
||||
levels: Map<string, QaMaturityTaxonomyLevel>;
|
||||
scoreSurfaces: Map<string, QaMaturityScoreSurface>;
|
||||
surfaces: QaMaturityTaxonomySurface[];
|
||||
}): string[] {
|
||||
const rows = [
|
||||
'<div className="maturity-surface-table">',
|
||||
' <div className="maturity-surface-row maturity-surface-row-header"><span>Surface</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Support</span></div>',
|
||||
];
|
||||
for (const surface of surfaces) {
|
||||
const scoreSurface = scoreSurfaces.get(surface.id);
|
||||
rows.push(
|
||||
' <div className="maturity-surface-row">',
|
||||
` <a className="maturity-surface-name" href="/maturity/taxonomy#${markdownSlug(surface.name)}"><span className="maturity-surface-title">${markdownEscape(surface.name)}</span><span className="maturity-surface-meta">${maturityLevelPillFromText(levelText(surface, levels))}<span>${surface.categories.length} areas</span></span></a>`,
|
||||
` <div className="maturity-surface-metric"><span className="maturity-surface-metric-label">Coverage</span>${scoreMeter(coverage.surfaces.get(surface.id))}</div>`,
|
||||
` <div className="maturity-surface-metric"><span className="maturity-surface-metric-label">Quality</span>${scoreMeter(scoreSurface?.scores?.quality)}</div>`,
|
||||
` <div className="maturity-surface-metric"><span className="maturity-surface-metric-label">Completeness</span>${scoreMeter(scoreSurface?.scores?.completeness)}</div>`,
|
||||
` <div className="maturity-surface-support">${maturityLtsBadge(scoreSurface?.lts)}</div>`,
|
||||
" </div>",
|
||||
);
|
||||
}
|
||||
rows.push("</div>");
|
||||
return rows;
|
||||
}
|
||||
|
||||
function renderSurfaceTabs({
|
||||
coverage,
|
||||
levels,
|
||||
scoreSurfaces,
|
||||
surfaces,
|
||||
}: {
|
||||
coverage: DerivedCoverageScores;
|
||||
levels: Map<string, QaMaturityTaxonomyLevel>;
|
||||
scoreSurfaces: Map<string, QaMaturityScoreSurface>;
|
||||
surfaces: QaMaturityTaxonomySurface[];
|
||||
}): string[] {
|
||||
const families = qaMaturityFamilyOrder(surfaces);
|
||||
const tabs = [
|
||||
"<Tabs>",
|
||||
' <Tab title="All surfaces">',
|
||||
...indentMarkdown(renderSurfaceRows({ coverage, levels, scoreSurfaces, surfaces })),
|
||||
" </Tab>",
|
||||
];
|
||||
for (const family of families) {
|
||||
tabs.push(
|
||||
` <Tab title="${markdownEscape(familyTitle(family))}">`,
|
||||
...indentMarkdown(
|
||||
renderSurfaceRows({
|
||||
coverage,
|
||||
levels,
|
||||
scoreSurfaces,
|
||||
surfaces: surfaces.filter((surface) => surface.family === family),
|
||||
}),
|
||||
),
|
||||
" </Tab>",
|
||||
);
|
||||
}
|
||||
tabs.push("</Tabs>");
|
||||
return tabs;
|
||||
}
|
||||
|
||||
function levelText(
|
||||
|
|
@ -296,31 +492,67 @@ function levelText(
|
|||
return [level?.code, level?.label ?? levelId].filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
function ltsText(lts?: QaMaturityScoreSurfaceLts): string {
|
||||
if (!lts || typeof lts !== "object") {
|
||||
return "unscored";
|
||||
function maturityLevelRank(
|
||||
surface: QaMaturityTaxonomySurface,
|
||||
taxonomyLevels: Map<string, QaMaturityTaxonomyLevel>,
|
||||
): number {
|
||||
const match = levelText(surface, taxonomyLevels).match(/M(\d+)/i);
|
||||
return match ? Number(match[1]) : -1;
|
||||
}
|
||||
|
||||
function ltsRank(lts?: QaMaturityScoreSurfaceLts): number {
|
||||
if (lts?.status === "full") {
|
||||
return 0;
|
||||
}
|
||||
const supportedCategories = lts.supported_categories ?? 0;
|
||||
if (lts.status === "full") {
|
||||
return `full (${supportedCategories})`;
|
||||
if (lts?.status === "partial") {
|
||||
return 1;
|
||||
}
|
||||
if (lts.status === "partial") {
|
||||
return `partial (${supportedCategories})`;
|
||||
}
|
||||
if (lts.status === "none") {
|
||||
return "none";
|
||||
}
|
||||
return lts.status ?? "unknown";
|
||||
return 2;
|
||||
}
|
||||
|
||||
function sortedMaturitySurfaces(
|
||||
surfaces: QaMaturityTaxonomySurface[],
|
||||
scoreSurfaces: Map<string, QaMaturityScoreSurface>,
|
||||
taxonomyLevels: Map<string, QaMaturityTaxonomyLevel>,
|
||||
): QaMaturityTaxonomySurface[] {
|
||||
return surfaces.toSorted((left, right) => {
|
||||
const leftScore = scoreSurfaces.get(left.id);
|
||||
const rightScore = scoreSurfaces.get(right.id);
|
||||
const levelOrder =
|
||||
maturityLevelRank(right, taxonomyLevels) - maturityLevelRank(left, taxonomyLevels);
|
||||
if (levelOrder !== 0) {
|
||||
return levelOrder;
|
||||
}
|
||||
const completenessOrder =
|
||||
(rightScore?.scores.completeness.score ?? -1) - (leftScore?.scores.completeness.score ?? -1);
|
||||
if (completenessOrder !== 0) {
|
||||
return completenessOrder;
|
||||
}
|
||||
const qualityOrder =
|
||||
(rightScore?.scores.quality.score ?? -1) - (leftScore?.scores.quality.score ?? -1);
|
||||
if (qualityOrder !== 0) {
|
||||
return qualityOrder;
|
||||
}
|
||||
const ltsOrder = ltsRank(leftScore?.lts) - ltsRank(rightScore?.lts);
|
||||
if (ltsOrder !== 0) {
|
||||
return ltsOrder;
|
||||
}
|
||||
return left.name.localeCompare(right.name);
|
||||
});
|
||||
}
|
||||
|
||||
function renderScoreBands(): string[] {
|
||||
return [
|
||||
"## Score bands",
|
||||
"",
|
||||
...markdownTable([
|
||||
["Label", "Score range"],
|
||||
...QA_MATURITY_SCORE_LABEL_BANDS.map(([label, low, high]) => [label, `${low}-${high}%`]),
|
||||
]),
|
||||
'<div className="maturity-band-list">',
|
||||
...[...QA_MATURITY_SCORE_LABEL_BANDS]
|
||||
.reverse()
|
||||
.map(
|
||||
([label, low, high]) =>
|
||||
` <div className="maturity-band ${maturityBandClass(label)}"><span className="maturity-band-title">${maturityLabelPill(label)}</span><span>${low}-${high}%</span></div>`,
|
||||
),
|
||||
"</div>",
|
||||
"",
|
||||
];
|
||||
}
|
||||
|
|
@ -643,41 +875,72 @@ function renderEvidenceSection(
|
|||
"",
|
||||
];
|
||||
|
||||
const summaryRows: RenderScalar[][] = [
|
||||
["Check set", "Completed", "Checks run", "Results", "Areas reviewed", "Capabilities reviewed"],
|
||||
];
|
||||
lines.push('<div className="maturity-evidence-grid">');
|
||||
for (const item of scorecardSummaries) {
|
||||
const scorecard = item.scorecard;
|
||||
summaryRows.push([
|
||||
markdownEscape(checkSetTitle(item.profile)),
|
||||
markdownEscape(item.generatedAt),
|
||||
item.entryCount,
|
||||
markdownEscape(resultCountsText(item.statuses)),
|
||||
markdownEscape(countText(scorecard?.categories)),
|
||||
markdownEscape(countText(scorecard?.features)),
|
||||
]);
|
||||
lines.push(
|
||||
' <div className="maturity-evidence-card">',
|
||||
` <span className="maturity-evidence-title">${markdownEscape(checkSetTitle(item.profile))}</span>`,
|
||||
` <span>${markdownEscape(item.generatedAt)}</span>`,
|
||||
` <span>${item.entryCount} checks - ${markdownEscape(resultCountsText(item.statuses))}</span>`,
|
||||
` <span>${markdownEscape(countText(scorecard?.categories))} areas - ${markdownEscape(countText(scorecard?.features))} capabilities</span>`,
|
||||
" </div>",
|
||||
);
|
||||
}
|
||||
lines.push(...markdownTable(summaryRows), "");
|
||||
lines.push("</div>", "");
|
||||
|
||||
const categoryRows = scorecardSummaries.flatMap((item) =>
|
||||
(item.scorecard?.categoryReports ?? []).map((category) => ({ item, category })),
|
||||
);
|
||||
if (categoryRows.length > 0) {
|
||||
const readinessRows: RenderScalar[][] = [
|
||||
["Check set", "Surface", "Area", "Status", "Capabilities reviewed", "Follow-up"],
|
||||
];
|
||||
for (const { item, category } of categoryRows) {
|
||||
const features = countText(category.features);
|
||||
readinessRows.push([
|
||||
markdownEscape(checkSetTitle(item.profile)),
|
||||
markdownEscape(surfaceNames.get(category.surfaceId) ?? familyTitle(category.surfaceId)),
|
||||
markdownEscape(category.name),
|
||||
markdownEscape(readinessStatusText(category.status)),
|
||||
markdownEscape(features),
|
||||
markdownEscape(followUpText(category.missingCoverageIds)),
|
||||
]);
|
||||
const grouped = new Map<string, Array<(typeof categoryRows)[number]>>();
|
||||
for (const row of categoryRows) {
|
||||
const existing = grouped.get(row.category.surfaceId) ?? [];
|
||||
existing.push(row);
|
||||
grouped.set(row.category.surfaceId, existing);
|
||||
}
|
||||
lines.push("### Readiness by area", "", ...markdownTable(readinessRows), "");
|
||||
lines.push(
|
||||
"### Readiness by area",
|
||||
"",
|
||||
"Open a surface to inspect the evidence state of each category. The list stays collapsed so the page remains useful at a glance.",
|
||||
"",
|
||||
"<AccordionGroup>",
|
||||
);
|
||||
for (const [surfaceId, rows] of grouped) {
|
||||
const surfaceName = surfaceNames.get(surfaceId) ?? familyTitle(surfaceId);
|
||||
const statusCounts = rows.reduce(
|
||||
(counts, row) => {
|
||||
counts[readinessStatusText(row.category.status)] =
|
||||
(counts[readinessStatusText(row.category.status)] ?? 0) + 1;
|
||||
return counts;
|
||||
},
|
||||
{} as Record<string, number>,
|
||||
);
|
||||
const summary = Object.entries(statusCounts)
|
||||
.map(([status, count]) => `${count} ${status.toLowerCase()}`)
|
||||
.join(" / ");
|
||||
lines.push(
|
||||
` <Accordion title="${markdownEscape(surfaceName)} - ${rows.length} areas">`,
|
||||
` <p className="maturity-readiness-summary">${markdownEscape(summary)}</p>`,
|
||||
' <div className="maturity-readiness-list">',
|
||||
' <div className="maturity-readiness-row maturity-readiness-row-header"><span>Area</span><span>Capabilities</span><span>Follow-up</span></div>',
|
||||
);
|
||||
for (const { item, category } of rows) {
|
||||
const status = readinessStatusText(category.status);
|
||||
lines.push(
|
||||
' <div className="maturity-readiness-row">',
|
||||
' <div className="maturity-readiness-area">',
|
||||
` <span className="maturity-readiness-title">${markdownEscape(category.name)}</span>`,
|
||||
` <span className="maturity-readiness-status maturity-readiness-status-${markdownSlug(status)}">${markdownEscape(status)} - ${markdownEscape(checkSetTitle(item.profile))}</span>`,
|
||||
" </div>",
|
||||
` <span>${markdownEscape(countText(category.features))}</span>`,
|
||||
` <span>${markdownEscape(followUpText(category.missingCoverageIds))}</span>`,
|
||||
" </div>",
|
||||
);
|
||||
}
|
||||
lines.push(" </div>", " </Accordion>", "");
|
||||
}
|
||||
lines.push("</AccordionGroup>", "");
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
|
@ -690,9 +953,16 @@ function renderMaturityScorecard({
|
|||
}: RenderMaturityScorecardInputs): string {
|
||||
const levels = qaMaturityTaxonomyLevelMap(taxonomy);
|
||||
const scoreSurfaces = surfaceScoreMap(scores);
|
||||
const surfaces = activeQaMaturityTaxonomySurfaces(taxonomy);
|
||||
const surfaces = sortedMaturitySurfaces(
|
||||
activeQaMaturityTaxonomySurfaces(taxonomy),
|
||||
scoreSurfaces,
|
||||
levels,
|
||||
);
|
||||
const surfaceNames = surfaceNameMap(surfaces);
|
||||
const updatedDate = latestScoreRunDate(scores);
|
||||
const surfaceAverage = coverage.rollups.surface_average;
|
||||
const qualityAverage = scores.rollups.surface_average.quality;
|
||||
const completenessAverage = scores.rollups.surface_average.completeness;
|
||||
const lines = [
|
||||
...frontmatter(
|
||||
"Maturity scorecard",
|
||||
|
|
@ -700,65 +970,44 @@ function renderMaturityScorecard({
|
|||
),
|
||||
"# Maturity scorecard",
|
||||
"",
|
||||
"These scores summarize release readiness across OpenClaw product areas, integrations, and supported workflows.",
|
||||
'<div className="maturity-hero">',
|
||||
' <p className="maturity-kicker">release readiness - generated from taxonomy + QA evidence</p>',
|
||||
' <p className="maturity-hero-title">A practical view of what is ready, what is proven, and what still needs work.</p>',
|
||||
` <p>${scores.counts.active_surfaces} surfaces - ${scores.counts.category_scores} capability areas - deterministic coverage plus human-reviewed quality and completeness.</p>`,
|
||||
' <p className="maturity-jump-links"><a href="#surface-explorer">Browse surfaces</a> / <a href="#qa-evidence-summary">Inspect QA evidence</a> / <a href="/maturity/taxonomy">Read the taxonomy</a></p>',
|
||||
"</div>",
|
||||
"",
|
||||
`The current scorecard covers ${scores.counts.active_surfaces} surfaces and ${scores.counts.category_scores} capability areas.`,
|
||||
"## What this page is for",
|
||||
"",
|
||||
"## Overall scores",
|
||||
"Use this page to answer one question: which OpenClaw surfaces are credible choices for a release, and what evidence supports that judgment? Coverage comes from deterministic QA evidence; quality and completeness are maintained as reviewed maturity scores.",
|
||||
"",
|
||||
...markdownTable([
|
||||
["Basis", "Coverage", "Quality", "Completeness"],
|
||||
[
|
||||
"Surface average",
|
||||
scoreText(coverage.rollups.surface_average),
|
||||
scoreText(scores.rollups.surface_average.quality),
|
||||
scoreText(scores.rollups.surface_average.completeness),
|
||||
],
|
||||
[
|
||||
"Category average",
|
||||
scoreText(coverage.rollups.category_average),
|
||||
scoreText(scores.rollups.category_average.quality),
|
||||
scoreText(scores.rollups.category_average.completeness),
|
||||
],
|
||||
]),
|
||||
"## At a glance",
|
||||
"",
|
||||
"- Coverage is derived from QA profile evidence.",
|
||||
"- Quality measures reliability and operational confidence.",
|
||||
"- Completeness measures how much of the expected user workflow is available.",
|
||||
'<div className="maturity-summary-grid">',
|
||||
...indentMarkdown(scoreSummary("Coverage", surfaceAverage, "QA profile evidence"), 2),
|
||||
...indentMarkdown(
|
||||
scoreSummary("Quality", qualityAverage, "Reliability and operator confidence"),
|
||||
2,
|
||||
),
|
||||
...indentMarkdown(
|
||||
scoreSummary("Completeness", completenessAverage, "Expected workflow coverage"),
|
||||
2,
|
||||
),
|
||||
"</div>",
|
||||
"",
|
||||
'Coverage is deliberately evidence-led: an area does not become "ready" just because the implementation exists.',
|
||||
"",
|
||||
...renderScoreBands(),
|
||||
];
|
||||
|
||||
const surfaceRows: RenderScalar[][] = [
|
||||
[
|
||||
"Surface",
|
||||
"Family",
|
||||
"Level",
|
||||
"Coverage",
|
||||
"Quality",
|
||||
"Completeness",
|
||||
"Long-term support",
|
||||
"Areas",
|
||||
],
|
||||
];
|
||||
for (const surface of surfaces) {
|
||||
const scoreSurface = scoreSurfaces.get(surface.id);
|
||||
const surfaceName = surface.name;
|
||||
surfaceRows.push([
|
||||
`[${markdownEscape(surfaceName)}](/maturity/taxonomy#${markdownSlug(surfaceName)})`,
|
||||
markdownEscape(familyTitle(surface.family)),
|
||||
markdownEscape(levelText(surface, levels)),
|
||||
scoreText(coverage.surfaces.get(surface.id)),
|
||||
scoreText(scoreSurface?.scores?.quality),
|
||||
scoreText(scoreSurface?.scores?.completeness),
|
||||
markdownEscape(ltsText(scoreSurface?.lts)),
|
||||
surface.categories.length,
|
||||
]);
|
||||
}
|
||||
lines.push(
|
||||
"## Surface scorecard",
|
||||
"## Surface explorer",
|
||||
"",
|
||||
...markdownTable(surfaceRows),
|
||||
'<a id="surface-explorer" />',
|
||||
"",
|
||||
"Surfaces are ordered by maturity level, completeness, and quality. LTS support is shown alongside each row so release-ready options are easy to compare.",
|
||||
"",
|
||||
...renderSurfaceTabs({ coverage, levels, scoreSurfaces, surfaces }),
|
||||
"",
|
||||
...renderEvidenceSection(evidenceSummaries, surfaceNames),
|
||||
);
|
||||
|
|
@ -776,7 +1025,11 @@ function renderTaxonomy({
|
|||
}: RenderInputs & { docsRouteIndex: DocsRouteIndex }): string {
|
||||
const levels = qaMaturityTaxonomyLevelMap(taxonomy);
|
||||
const scoreSurfaces = surfaceScoreMap(scores);
|
||||
const surfaces = activeQaMaturityTaxonomySurfaces(taxonomy);
|
||||
const surfaces = sortedMaturitySurfaces(
|
||||
activeQaMaturityTaxonomySurfaces(taxonomy),
|
||||
scoreSurfaces,
|
||||
levels,
|
||||
);
|
||||
const lines = [
|
||||
...frontmatter(
|
||||
"Maturity taxonomy",
|
||||
|
|
@ -784,50 +1037,64 @@ function renderTaxonomy({
|
|||
),
|
||||
"# Maturity taxonomy",
|
||||
"",
|
||||
"This page explains the product areas and capability groups behind the maturity scorecard.",
|
||||
'<div className="maturity-hero maturity-hero-compact">',
|
||||
' <p className="maturity-kicker">the model behind the scorecard</p>',
|
||||
' <p className="maturity-hero-title">Surfaces > categories > capabilities > evidence.</p>',
|
||||
` <p>${surfaces.length} surfaces grouped into ${qaMaturityFamilyOrder(surfaces).length} families, with every category tied back to canonical docs and QA coverage IDs.</p>`,
|
||||
' <p className="maturity-jump-links"><a href="#product-areas">Browse product areas</a> / <a href="#taxonomy-details">Open detailed taxonomy</a> / <a href="/maturity/scorecard">View scores</a></p>',
|
||||
"</div>",
|
||||
"",
|
||||
"## How to read this page",
|
||||
"",
|
||||
"A surface is a product area such as Gateway runtime, Discord, or the macOS app. Each surface contains categories, and each category contains the capability-level checks that QA scenarios cover. Use the scorecard for release-level judgment; use this page to inspect the model underneath it.",
|
||||
"",
|
||||
"## Maturity levels",
|
||||
"",
|
||||
...markdownTable([
|
||||
["Level", "Label", "Meaning", "Promotion bar"],
|
||||
...taxonomy.levels.map((level) => [
|
||||
yamlCode(level.code ?? level.id),
|
||||
markdownEscape(level.label ?? level.id),
|
||||
markdownEscape(level.meaning ?? ""),
|
||||
markdownEscape(level.promotion_bar ?? ""),
|
||||
]),
|
||||
]),
|
||||
'<div className="maturity-level-list">',
|
||||
...taxonomy.levels.map(
|
||||
(level) =>
|
||||
` <div className="maturity-level-row"><span className="maturity-level-title">${maturityLevelPill(level.code ?? level.id, level.label ?? level.id)}</span><span>${markdownEscape(level.meaning ?? "")}</span><span className="maturity-level-promotion">Promotion: ${markdownEscape(level.promotion_bar ?? "")}</span></div>`,
|
||||
),
|
||||
"</div>",
|
||||
"",
|
||||
"## Product areas",
|
||||
"",
|
||||
'<a id="product-areas" />',
|
||||
"",
|
||||
];
|
||||
|
||||
for (const family of qaMaturityFamilyOrder(surfaces)) {
|
||||
lines.push(`### ${familyTitle(family)} surfaces`, "");
|
||||
const families = qaMaturityFamilyOrder(surfaces);
|
||||
lines.push("<Tabs>");
|
||||
for (const family of families) {
|
||||
lines.push(` <Tab title="${markdownEscape(familyTitle(family))}">`, "");
|
||||
for (const surface of surfaces.filter((candidate) => candidate.family === family)) {
|
||||
const surfaceName = surface.name;
|
||||
lines.push(`- [${markdownEscape(surfaceName)}](#${markdownSlug(surfaceName)})`);
|
||||
const scoreSurface = scoreSurfaces.get(surface.id);
|
||||
lines.push(
|
||||
...indentMarkdown(
|
||||
[
|
||||
`<a className="maturity-surface-link" href="#${markdownSlug(surface.name)}">`,
|
||||
` <span className="maturity-surface-title">${markdownEscape(surface.name)}</span>`,
|
||||
` <span className="maturity-surface-meta">${maturityLevelPillFromText(levelText(surface, levels))}<span>${surface.categories.length} areas - ${scorePercent(scoreSurface?.scores?.completeness) ?? "-"}% complete</span></span>`,
|
||||
"</a>",
|
||||
],
|
||||
4,
|
||||
),
|
||||
);
|
||||
lines.push("");
|
||||
}
|
||||
lines.push("");
|
||||
lines.push(" </Tab>");
|
||||
}
|
||||
lines.push("</Tabs>", "", "## Details", "", '<a id="taxonomy-details" />', "");
|
||||
|
||||
lines.push("## Details", "");
|
||||
for (const family of qaMaturityFamilyOrder(surfaces)) {
|
||||
lines.push(`### ${familyTitle(family)}`, "");
|
||||
for (const family of families) {
|
||||
lines.push(`### ${familyTitle(family)}`, "", "<AccordionGroup>");
|
||||
for (const surface of surfaces.filter((candidate) => candidate.family === family)) {
|
||||
const surfaceName = surface.name;
|
||||
const scoreSurface = scoreSurfaces.get(surface.id);
|
||||
const categoryScores = categoryScoreMap(scoreSurface);
|
||||
const categoryRows: RenderScalar[][] = [
|
||||
[
|
||||
"Area",
|
||||
"Capabilities",
|
||||
"Docs",
|
||||
"Coverage",
|
||||
"Quality",
|
||||
"Completeness",
|
||||
"Long-term support",
|
||||
],
|
||||
const categoryLines = [
|
||||
'<div className="maturity-category-list">',
|
||||
' <div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>',
|
||||
];
|
||||
for (const category of surface.categories) {
|
||||
const docs = (category.docs ?? [])
|
||||
|
|
@ -838,26 +1105,39 @@ function renderTaxonomy({
|
|||
const coverageScore = coverage.categories.get(
|
||||
qaMaturityCoverageCategoryKey(surface.id, category.name),
|
||||
);
|
||||
categoryRows.push([
|
||||
markdownEscape(category.name),
|
||||
category.features.length,
|
||||
docs,
|
||||
scoreText(coverageScore),
|
||||
scoreText(scoreCategory?.quality),
|
||||
scoreText(scoreCategory?.completeness),
|
||||
markdownEscape(scoreCategory?.lts?.supported ? "Yes" : "No"),
|
||||
]);
|
||||
categoryLines.push(
|
||||
' <div className="maturity-category-row">',
|
||||
' <div className="maturity-category-area">',
|
||||
` <span className="maturity-category-title">${markdownEscape(category.name)}</span>`,
|
||||
` <span>${category.features.length} capabilities${scoreCategory?.lts?.supported ? " / LTS-supported" : ""}</span>`,
|
||||
" </div>",
|
||||
` <div>${scoreMeter(coverageScore)}</div>`,
|
||||
` <div>${scoreMeter(scoreCategory?.quality)}</div>`,
|
||||
` <div>${scoreMeter(scoreCategory?.completeness)}</div>`,
|
||||
` <div className="maturity-category-docs">${docs || "No linked docs"}</div>`,
|
||||
" </div>",
|
||||
);
|
||||
}
|
||||
categoryLines.push("</div>");
|
||||
lines.push(
|
||||
`#### ${surfaceName}`,
|
||||
` <Accordion title="${markdownEscape(surfaceName)} - ${markdownEscape(levelText(surface, levels))} - ${surface.categories.length} areas">`,
|
||||
` <a id="${markdownSlug(surfaceName)}" />`,
|
||||
"",
|
||||
`- Level: ${markdownEscape(levelText(surface, levels))}`,
|
||||
`- Rationale: ${surface.rationale ?? ""}`,
|
||||
` ${markdownEscape(surface.rationale ?? "")}`,
|
||||
"",
|
||||
...markdownTable(categoryRows),
|
||||
...indentMarkdown(
|
||||
[
|
||||
`<div className="maturity-surface-rollup"><span>Coverage ${scoreLabel(coverage.surfaces.get(surface.id))}</span><span>Quality ${scoreLabel(scoreSurface?.scores?.quality)}</span><span>Completeness ${scoreLabel(scoreSurface?.scores?.completeness)}</span><span>${maturityLtsBadge(scoreSurface?.lts)}</span></div>`,
|
||||
"",
|
||||
...categoryLines,
|
||||
],
|
||||
4,
|
||||
),
|
||||
" </Accordion>",
|
||||
);
|
||||
lines.push("");
|
||||
}
|
||||
lines.push("</AccordionGroup>", "");
|
||||
}
|
||||
return `${lines.join("\n").trimEnd()}\n`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue