@messagevisor/catalog 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/index-DrsX4U8c.css +1 -0
- package/dist/assets/index-OXIn4K-M.js +73 -0
- package/dist/index.html +2 -2
- package/lib/node/index.js +210 -100
- package/lib/node/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/details/FieldGrid.tsx +1 -1
- package/src/components/details/GroupSegmentTree.tsx +2 -1
- package/src/components/details/UsageLinks.tsx +3 -2
- package/src/components/history/HistoryTimeline.tsx +4 -3
- package/src/components/layout/PageHeader.tsx +13 -3
- package/src/components/lists/EntityList.tsx +2 -1
- package/src/components/ui/EntityKey.tsx +20 -0
- package/src/node/index.spec.ts +222 -0
- package/src/node/index.ts +241 -97
- package/src/pages/EntityDetailPage.tsx +14 -5
- package/dist/assets/index-CfGbXx4X.css +0 -1
- package/dist/assets/index-r8ugP5JL.js +0 -73
package/src/node/index.ts
CHANGED
|
@@ -241,7 +241,7 @@ interface CatalogBuildContext {
|
|
|
241
241
|
repositoryRootDirectoryPath: string;
|
|
242
242
|
outputDirectoryPath: string;
|
|
243
243
|
dataDirectoryPath: string;
|
|
244
|
-
|
|
244
|
+
historyIndex: CatalogHistoryIndex;
|
|
245
245
|
runtime: CatalogRuntime;
|
|
246
246
|
devEditors: CatalogDevEditor[];
|
|
247
247
|
duplicateResultsBySet: Record<string, CatalogDuplicateTranslationsSetResult>;
|
|
@@ -258,6 +258,21 @@ interface EntityPathInfo {
|
|
|
258
258
|
set?: string;
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
interface CatalogHistoryIndex {
|
|
262
|
+
entries: CatalogHistoryEntry[];
|
|
263
|
+
bySet: Record<string, CatalogHistoryEntry[]>;
|
|
264
|
+
byEntity: Record<string, CatalogHistoryEntry[]>;
|
|
265
|
+
lastModifiedByEntity: Record<string, CatalogLastModified>;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
interface StreamingGitCommit {
|
|
269
|
+
commit: string;
|
|
270
|
+
author: string;
|
|
271
|
+
timestamp: string;
|
|
272
|
+
entities: CatalogHistoryEntity[];
|
|
273
|
+
seenEntityKeys: Set<string>;
|
|
274
|
+
}
|
|
275
|
+
|
|
261
276
|
function toPosixPath(value: string) {
|
|
262
277
|
return value.split(path.sep).join("/");
|
|
263
278
|
}
|
|
@@ -393,25 +408,11 @@ function getTargetMessageKeys(target: Target, messageKeys: string[]) {
|
|
|
393
408
|
.sort();
|
|
394
409
|
}
|
|
395
410
|
|
|
396
|
-
function
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
key: string,
|
|
400
|
-
set?: string,
|
|
401
|
-
): CatalogLastModified | undefined {
|
|
402
|
-
const entry = history.find((candidate) =>
|
|
403
|
-
candidate.entities.some(
|
|
404
|
-
(entity) =>
|
|
405
|
-
entity.type === type &&
|
|
406
|
-
entity.key === key &&
|
|
407
|
-
(typeof set === "undefined" || entity.set === set),
|
|
408
|
-
),
|
|
409
|
-
);
|
|
410
|
-
|
|
411
|
-
if (!entry) {
|
|
412
|
-
return undefined;
|
|
413
|
-
}
|
|
411
|
+
function getHistoryEntityKey(type: CatalogEntityType | "test", key: string, set?: string) {
|
|
412
|
+
return `${set || ""}\x1f${type}\x1f${key}`;
|
|
413
|
+
}
|
|
414
414
|
|
|
415
|
+
function toLastModified(entry: CatalogHistoryEntry): CatalogLastModified {
|
|
415
416
|
return {
|
|
416
417
|
commit: entry.commit,
|
|
417
418
|
author: entry.author,
|
|
@@ -419,11 +420,20 @@ function getLastModified(
|
|
|
419
420
|
};
|
|
420
421
|
}
|
|
421
422
|
|
|
423
|
+
function getLastModified(
|
|
424
|
+
historyIndex: CatalogHistoryIndex,
|
|
425
|
+
type: CatalogEntityType,
|
|
426
|
+
key: string,
|
|
427
|
+
set?: string,
|
|
428
|
+
): CatalogLastModified | undefined {
|
|
429
|
+
return historyIndex.lastModifiedByEntity[getHistoryEntityKey(type, key, set)];
|
|
430
|
+
}
|
|
431
|
+
|
|
422
432
|
function getEntitySummary(
|
|
423
433
|
entity: Locale | Message | Attribute | Segment | Target,
|
|
424
434
|
type: CatalogEntityType,
|
|
425
435
|
key: string,
|
|
426
|
-
|
|
436
|
+
historyIndex: CatalogHistoryIndex,
|
|
427
437
|
set?: string,
|
|
428
438
|
extra: Partial<CatalogEntitySummary> = {},
|
|
429
439
|
): CatalogEntitySummary {
|
|
@@ -433,7 +443,7 @@ function getEntitySummary(
|
|
|
433
443
|
archived: (entity as any).archived,
|
|
434
444
|
deprecated: (entity as any).deprecated,
|
|
435
445
|
...extra,
|
|
436
|
-
lastModified: getLastModified(
|
|
446
|
+
lastModified: getLastModified(historyIndex, type, key, set),
|
|
437
447
|
href: `entities/${type}/${encodeKey(key)}.json`,
|
|
438
448
|
};
|
|
439
449
|
}
|
|
@@ -710,6 +720,186 @@ function runGit(rootDirectoryPath: string, args: string[]) {
|
|
|
710
720
|
});
|
|
711
721
|
}
|
|
712
722
|
|
|
723
|
+
function getCatalogHistoryPathPatterns(rootDirectoryPath: string, projectConfig: any) {
|
|
724
|
+
return projectConfig.sets
|
|
725
|
+
? [path.relative(rootDirectoryPath, projectConfig.setsDirectoryPath)]
|
|
726
|
+
: [
|
|
727
|
+
path.relative(rootDirectoryPath, projectConfig.localesDirectoryPath),
|
|
728
|
+
path.relative(rootDirectoryPath, projectConfig.messagesDirectoryPath),
|
|
729
|
+
path.relative(rootDirectoryPath, projectConfig.attributesDirectoryPath),
|
|
730
|
+
path.relative(rootDirectoryPath, projectConfig.segmentsDirectoryPath),
|
|
731
|
+
path.relative(rootDirectoryPath, projectConfig.targetsDirectoryPath),
|
|
732
|
+
path.relative(rootDirectoryPath, projectConfig.testsDirectoryPath),
|
|
733
|
+
];
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function createEmptyHistoryIndex(): CatalogHistoryIndex {
|
|
737
|
+
return {
|
|
738
|
+
entries: [],
|
|
739
|
+
bySet: {},
|
|
740
|
+
byEntity: {},
|
|
741
|
+
lastModifiedByEntity: {},
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function addHistoryIndexEntry(
|
|
746
|
+
target: Record<string, CatalogHistoryEntry[]>,
|
|
747
|
+
key: string,
|
|
748
|
+
entry: CatalogHistoryEntry,
|
|
749
|
+
) {
|
|
750
|
+
if (!target[key]) {
|
|
751
|
+
target[key] = [];
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
target[key].push(entry);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function buildCatalogHistoryIndex(entries: CatalogHistoryEntry[]): CatalogHistoryIndex {
|
|
758
|
+
const index = createEmptyHistoryIndex();
|
|
759
|
+
index.entries = entries;
|
|
760
|
+
|
|
761
|
+
for (const entry of entries) {
|
|
762
|
+
const seenSets = new Set<string>();
|
|
763
|
+
|
|
764
|
+
for (const entity of entry.entities) {
|
|
765
|
+
if (entity.type === "test") {
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const entityKey = getHistoryEntityKey(entity.type, entity.key, entity.set);
|
|
770
|
+
addHistoryIndexEntry(index.byEntity, entityKey, entry);
|
|
771
|
+
|
|
772
|
+
if (!index.lastModifiedByEntity[entityKey]) {
|
|
773
|
+
index.lastModifiedByEntity[entityKey] = toLastModified(entry);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
if (entity.set && !seenSets.has(entity.set)) {
|
|
777
|
+
seenSets.add(entity.set);
|
|
778
|
+
addHistoryIndexEntry(index.bySet, entity.set, entry);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return index;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function appendHistoryEntry(
|
|
787
|
+
history: CatalogHistoryEntry[],
|
|
788
|
+
current: StreamingGitCommit | undefined,
|
|
789
|
+
) {
|
|
790
|
+
if (!current || current.entities.length === 0) {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
history.push({
|
|
795
|
+
commit: current.commit,
|
|
796
|
+
author: current.author,
|
|
797
|
+
timestamp: current.timestamp,
|
|
798
|
+
entities: current.entities,
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function addHistoryEntity(current: StreamingGitCommit, entity: CatalogHistoryEntity) {
|
|
803
|
+
if (entity.type === "test") {
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const entityKey = getHistoryEntityKey(entity.type, entity.key, entity.set);
|
|
808
|
+
|
|
809
|
+
if (current.seenEntityKeys.has(entityKey)) {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
current.seenEntityKeys.add(entityKey);
|
|
814
|
+
current.entities.push(entity);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
async function streamCatalogGitHistory(
|
|
818
|
+
rootDirectoryPath: string,
|
|
819
|
+
projectConfig: any,
|
|
820
|
+
): Promise<CatalogHistoryEntry[]> {
|
|
821
|
+
const pathPatterns = getCatalogHistoryPathPatterns(rootDirectoryPath, projectConfig);
|
|
822
|
+
const args = [
|
|
823
|
+
"-C",
|
|
824
|
+
rootDirectoryPath,
|
|
825
|
+
"log",
|
|
826
|
+
"--name-only",
|
|
827
|
+
"--pretty=format:%x1e%h%x1f%an%x1f%aI",
|
|
828
|
+
"--relative",
|
|
829
|
+
"--no-merges",
|
|
830
|
+
"--",
|
|
831
|
+
...pathPatterns,
|
|
832
|
+
];
|
|
833
|
+
|
|
834
|
+
return new Promise((resolve, reject) => {
|
|
835
|
+
let current: StreamingGitCommit | undefined;
|
|
836
|
+
let buffer = "";
|
|
837
|
+
const history: CatalogHistoryEntry[] = [];
|
|
838
|
+
const git = childProcess.spawn("git", args, {
|
|
839
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
function processLine(line: string) {
|
|
843
|
+
if (!line) {
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (line.startsWith("\x1e")) {
|
|
848
|
+
appendHistoryEntry(history, current);
|
|
849
|
+
|
|
850
|
+
const [commit, author, timestamp] = line.slice(1).split("\x1f");
|
|
851
|
+
current =
|
|
852
|
+
commit && author && timestamp
|
|
853
|
+
? {
|
|
854
|
+
commit,
|
|
855
|
+
author,
|
|
856
|
+
timestamp,
|
|
857
|
+
entities: [],
|
|
858
|
+
seenEntityKeys: new Set(),
|
|
859
|
+
}
|
|
860
|
+
: undefined;
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (!current) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
const entity = getEntityInfoFromRelativePath(rootDirectoryPath, projectConfig, line);
|
|
869
|
+
|
|
870
|
+
if (entity) {
|
|
871
|
+
addHistoryEntity(current, entity);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
git.stdout.setEncoding("utf8");
|
|
876
|
+
git.stdout.on("data", (chunk: string) => {
|
|
877
|
+
buffer += chunk;
|
|
878
|
+
const lines = buffer.split(/\r?\n/);
|
|
879
|
+
buffer = lines.pop() || "";
|
|
880
|
+
|
|
881
|
+
for (const line of lines) {
|
|
882
|
+
processLine(line);
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
git.on("error", reject);
|
|
886
|
+
git.on("close", (code) => {
|
|
887
|
+
if (buffer) {
|
|
888
|
+
processLine(buffer);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
appendHistoryEntry(history, current);
|
|
892
|
+
|
|
893
|
+
if (code === 0) {
|
|
894
|
+
resolve(history);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
reject(new Error(`git log exited with code ${code}`));
|
|
899
|
+
});
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
|
|
713
903
|
function isExecutableFile(filePath: string) {
|
|
714
904
|
try {
|
|
715
905
|
const stat = fs.statSync(filePath);
|
|
@@ -791,51 +981,16 @@ function detectDevEditors(): CatalogDevEditor[] {
|
|
|
791
981
|
return editors;
|
|
792
982
|
}
|
|
793
983
|
|
|
794
|
-
function
|
|
984
|
+
async function getGitHistoryIndex(
|
|
985
|
+
rootDirectoryPath: string,
|
|
986
|
+
projectConfig: any,
|
|
987
|
+
): Promise<CatalogHistoryIndex> {
|
|
795
988
|
try {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
path.relative(rootDirectoryPath, projectConfig.localesDirectoryPath),
|
|
800
|
-
path.relative(rootDirectoryPath, projectConfig.messagesDirectoryPath),
|
|
801
|
-
path.relative(rootDirectoryPath, projectConfig.attributesDirectoryPath),
|
|
802
|
-
path.relative(rootDirectoryPath, projectConfig.segmentsDirectoryPath),
|
|
803
|
-
path.relative(rootDirectoryPath, projectConfig.targetsDirectoryPath),
|
|
804
|
-
path.relative(rootDirectoryPath, projectConfig.testsDirectoryPath),
|
|
805
|
-
];
|
|
806
|
-
const raw = runGit(rootDirectoryPath, [
|
|
807
|
-
"log",
|
|
808
|
-
"--name-only",
|
|
809
|
-
"--pretty=format:%h|%an|%aI",
|
|
810
|
-
"--relative",
|
|
811
|
-
"--no-merges",
|
|
812
|
-
"--",
|
|
813
|
-
...pathPatterns,
|
|
814
|
-
]);
|
|
815
|
-
const blocks = raw.split("\n\n");
|
|
816
|
-
const history: CatalogHistoryEntry[] = [];
|
|
817
|
-
|
|
818
|
-
for (const block of blocks) {
|
|
819
|
-
if (!block.trim()) {
|
|
820
|
-
continue;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
const lines = block.split("\n").filter(Boolean);
|
|
824
|
-
const [commit, author, timestamp] = lines[0].split("|");
|
|
825
|
-
const entities = lines
|
|
826
|
-
.slice(1)
|
|
827
|
-
.map((line) => getEntityInfoFromRelativePath(rootDirectoryPath, projectConfig, line))
|
|
828
|
-
.filter(Boolean) as CatalogHistoryEntity[];
|
|
829
|
-
const filteredEntities = entities.filter((entity) => entity.type !== "test");
|
|
830
|
-
|
|
831
|
-
if (filteredEntities.length > 0) {
|
|
832
|
-
history.push({ commit, author, timestamp, entities: filteredEntities });
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
return history;
|
|
989
|
+
return buildCatalogHistoryIndex(
|
|
990
|
+
await streamCatalogGitHistory(rootDirectoryPath, projectConfig),
|
|
991
|
+
);
|
|
837
992
|
} catch (_error) {
|
|
838
|
-
return
|
|
993
|
+
return createEmptyHistoryIndex();
|
|
839
994
|
}
|
|
840
995
|
}
|
|
841
996
|
|
|
@@ -946,24 +1101,13 @@ async function writeHistoryPages(directoryPath: string, history: CatalogHistoryE
|
|
|
946
1101
|
}
|
|
947
1102
|
}
|
|
948
1103
|
|
|
949
|
-
function
|
|
950
|
-
|
|
1104
|
+
function getHistoryForEntity(
|
|
1105
|
+
historyIndex: CatalogHistoryIndex,
|
|
951
1106
|
type: CatalogEntityType,
|
|
952
1107
|
key: string,
|
|
953
1108
|
set?: string,
|
|
954
1109
|
) {
|
|
955
|
-
return
|
|
956
|
-
entry.entities.some(
|
|
957
|
-
(entity) =>
|
|
958
|
-
entity.type === type &&
|
|
959
|
-
entity.key === key &&
|
|
960
|
-
(typeof set === "undefined" || entity.set === set),
|
|
961
|
-
),
|
|
962
|
-
);
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
function filterHistoryForSet(history: CatalogHistoryEntry[], set: string) {
|
|
966
|
-
return history.filter((entry) => entry.entities.some((entity) => entity.set === set));
|
|
1110
|
+
return historyIndex.byEntity[getHistoryEntityKey(type, key, set)] || [];
|
|
967
1111
|
}
|
|
968
1112
|
|
|
969
1113
|
function getSourceFileInfo(
|
|
@@ -1137,7 +1281,7 @@ async function buildSetCatalog(
|
|
|
1137
1281
|
}
|
|
1138
1282
|
}
|
|
1139
1283
|
|
|
1140
|
-
const history = set ?
|
|
1284
|
+
const history = set ? context.historyIndex.bySet[set] || [] : context.historyIndex.entries;
|
|
1141
1285
|
const localeDirections = getLocaleDirections(locales);
|
|
1142
1286
|
const duplicateResult = context.duplicateResultsBySet[getDuplicateSetKey(set)] || {
|
|
1143
1287
|
set: set || null,
|
|
@@ -1225,11 +1369,11 @@ async function buildSetCatalog(
|
|
|
1225
1369
|
context.runtime.resolveFormats(localeKey, locales, targets[targetKey]),
|
|
1226
1370
|
]),
|
|
1227
1371
|
),
|
|
1228
|
-
lastModified: getLastModified(context.
|
|
1372
|
+
lastModified: getLastModified(context.historyIndex, "locale", localeKey, set || undefined),
|
|
1229
1373
|
};
|
|
1230
1374
|
|
|
1231
1375
|
index.entities.locale.push(
|
|
1232
|
-
getEntitySummary(locale, "locale", localeKey, context.
|
|
1376
|
+
getEntitySummary(locale, "locale", localeKey, context.historyIndex, set || undefined, {
|
|
1233
1377
|
targets: sortStrings(Array.from(localeTargets[localeKey] || [])),
|
|
1234
1378
|
}),
|
|
1235
1379
|
);
|
|
@@ -1243,7 +1387,7 @@ async function buildSetCatalog(
|
|
|
1243
1387
|
);
|
|
1244
1388
|
await writeHistoryPages(
|
|
1245
1389
|
path.join(outputDirectoryPath, "history", "locale", encodeKey(localeKey)),
|
|
1246
|
-
|
|
1390
|
+
getHistoryForEntity(context.historyIndex, "locale", localeKey, set || undefined),
|
|
1247
1391
|
);
|
|
1248
1392
|
}
|
|
1249
1393
|
|
|
@@ -1305,7 +1449,7 @@ async function buildSetCatalog(
|
|
|
1305
1449
|
resolveTranslationRow(override.translations, localeKey, locales),
|
|
1306
1450
|
),
|
|
1307
1451
|
})),
|
|
1308
|
-
lastModified: getLastModified(context.
|
|
1452
|
+
lastModified: getLastModified(context.historyIndex, "message", messageKey, set || undefined),
|
|
1309
1453
|
};
|
|
1310
1454
|
|
|
1311
1455
|
const directLocales = localeKeys.filter(
|
|
@@ -1334,7 +1478,7 @@ async function buildSetCatalog(
|
|
|
1334
1478
|
}
|
|
1335
1479
|
|
|
1336
1480
|
index.entities.message.push(
|
|
1337
|
-
getEntitySummary(message, "message", messageKey, context.
|
|
1481
|
+
getEntitySummary(message, "message", messageKey, context.historyIndex, set || undefined, {
|
|
1338
1482
|
targets: sortStrings(messageTargets[messageKey] || []),
|
|
1339
1483
|
...(directLocales.length > 0 ? { locales: sortStrings(directLocales) } : {}),
|
|
1340
1484
|
...(overrideLocalesList.length > 0 ? { overrideLocales: overrideLocalesList } : {}),
|
|
@@ -1346,7 +1490,7 @@ async function buildSetCatalog(
|
|
|
1346
1490
|
);
|
|
1347
1491
|
await writeHistoryPages(
|
|
1348
1492
|
path.join(outputDirectoryPath, "history", "message", encodeKey(messageKey)),
|
|
1349
|
-
|
|
1493
|
+
getHistoryForEntity(context.historyIndex, "message", messageKey, set || undefined),
|
|
1350
1494
|
);
|
|
1351
1495
|
}
|
|
1352
1496
|
|
|
@@ -1378,7 +1522,7 @@ async function buildSetCatalog(
|
|
|
1378
1522
|
messages: sortStrings(Array.from(attributesUsedInMessages[attributeKey] || [])),
|
|
1379
1523
|
},
|
|
1380
1524
|
lastModified: getLastModified(
|
|
1381
|
-
context.
|
|
1525
|
+
context.historyIndex,
|
|
1382
1526
|
"attribute",
|
|
1383
1527
|
attributeKey,
|
|
1384
1528
|
set || undefined,
|
|
@@ -1390,7 +1534,7 @@ async function buildSetCatalog(
|
|
|
1390
1534
|
attribute,
|
|
1391
1535
|
"attribute",
|
|
1392
1536
|
attributeKey,
|
|
1393
|
-
context.
|
|
1537
|
+
context.historyIndex,
|
|
1394
1538
|
set || undefined,
|
|
1395
1539
|
{
|
|
1396
1540
|
targets: sortStrings(Array.from(attributeTargets[attributeKey] || [])),
|
|
@@ -1403,7 +1547,7 @@ async function buildSetCatalog(
|
|
|
1403
1547
|
);
|
|
1404
1548
|
await writeHistoryPages(
|
|
1405
1549
|
path.join(outputDirectoryPath, "history", "attribute", encodeKey(attributeKey)),
|
|
1406
|
-
|
|
1550
|
+
getHistoryForEntity(context.historyIndex, "attribute", attributeKey, set || undefined),
|
|
1407
1551
|
);
|
|
1408
1552
|
}
|
|
1409
1553
|
|
|
@@ -1428,11 +1572,11 @@ async function buildSetCatalog(
|
|
|
1428
1572
|
attributes: sortStrings(Array.from(usedAttributes)),
|
|
1429
1573
|
messages: sortStrings(Array.from(segmentsUsedInMessages[segmentKey] || [])),
|
|
1430
1574
|
},
|
|
1431
|
-
lastModified: getLastModified(context.
|
|
1575
|
+
lastModified: getLastModified(context.historyIndex, "segment", segmentKey, set || undefined),
|
|
1432
1576
|
};
|
|
1433
1577
|
|
|
1434
1578
|
index.entities.segment.push(
|
|
1435
|
-
getEntitySummary(segment, "segment", segmentKey, context.
|
|
1579
|
+
getEntitySummary(segment, "segment", segmentKey, context.historyIndex, set || undefined, {
|
|
1436
1580
|
targets: sortStrings(Array.from(segmentTargets[segmentKey] || [])),
|
|
1437
1581
|
}),
|
|
1438
1582
|
);
|
|
@@ -1442,7 +1586,7 @@ async function buildSetCatalog(
|
|
|
1442
1586
|
);
|
|
1443
1587
|
await writeHistoryPages(
|
|
1444
1588
|
path.join(outputDirectoryPath, "history", "segment", encodeKey(segmentKey)),
|
|
1445
|
-
|
|
1589
|
+
getHistoryForEntity(context.historyIndex, "segment", segmentKey, set || undefined),
|
|
1446
1590
|
);
|
|
1447
1591
|
}
|
|
1448
1592
|
|
|
@@ -1474,11 +1618,11 @@ async function buildSetCatalog(
|
|
|
1474
1618
|
formatsByLocale,
|
|
1475
1619
|
formatRowsByLocale,
|
|
1476
1620
|
messages: targetMessages[targetKey],
|
|
1477
|
-
lastModified: getLastModified(context.
|
|
1621
|
+
lastModified: getLastModified(context.historyIndex, "target", targetKey, set || undefined),
|
|
1478
1622
|
};
|
|
1479
1623
|
|
|
1480
1624
|
index.entities.target.push(
|
|
1481
|
-
getEntitySummary(target, "target", targetKey, context.
|
|
1625
|
+
getEntitySummary(target, "target", targetKey, context.historyIndex, set || undefined, {
|
|
1482
1626
|
messageCount: targetMessages[targetKey].length,
|
|
1483
1627
|
}),
|
|
1484
1628
|
);
|
|
@@ -1488,7 +1632,7 @@ async function buildSetCatalog(
|
|
|
1488
1632
|
);
|
|
1489
1633
|
await writeHistoryPages(
|
|
1490
1634
|
path.join(outputDirectoryPath, "history", "target", encodeKey(targetKey)),
|
|
1491
|
-
|
|
1635
|
+
getHistoryForEntity(context.historyIndex, "target", targetKey, set || undefined),
|
|
1492
1636
|
);
|
|
1493
1637
|
}
|
|
1494
1638
|
|
|
@@ -1543,7 +1687,7 @@ export async function exportCatalog(
|
|
|
1543
1687
|
}
|
|
1544
1688
|
|
|
1545
1689
|
const devEditors = options.dev ? options.devEditors || detectDevEditors() : [];
|
|
1546
|
-
const
|
|
1690
|
+
const historyIndex = await getGitHistoryIndex(rootDirectoryPath, projectConfig);
|
|
1547
1691
|
const duplicateTranslations = await runtime.findDuplicateTranslations(projectConfig, datasource);
|
|
1548
1692
|
const duplicateResultsBySet = Object.fromEntries(
|
|
1549
1693
|
duplicateTranslations.results.map((result) => [getDuplicateSetKey(result.set), result]),
|
|
@@ -1553,7 +1697,7 @@ export async function exportCatalog(
|
|
|
1553
1697
|
repositoryRootDirectoryPath: getRepositoryRootDirectoryPath(rootDirectoryPath),
|
|
1554
1698
|
outputDirectoryPath,
|
|
1555
1699
|
dataDirectoryPath,
|
|
1556
|
-
|
|
1700
|
+
historyIndex,
|
|
1557
1701
|
runtime,
|
|
1558
1702
|
devEditors,
|
|
1559
1703
|
duplicateResultsBySet,
|
|
@@ -1561,7 +1705,7 @@ export async function exportCatalog(
|
|
|
1561
1705
|
const executions = await runtime.getProjectSetExecutions(projectConfig, datasource);
|
|
1562
1706
|
const setIndexes: Record<string, CatalogSetIndex> = {};
|
|
1563
1707
|
|
|
1564
|
-
await writeHistoryPages(path.join(dataDirectoryPath, "project", "history"),
|
|
1708
|
+
await writeHistoryPages(path.join(dataDirectoryPath, "project", "history"), historyIndex.entries);
|
|
1565
1709
|
|
|
1566
1710
|
for (const execution of executions) {
|
|
1567
1711
|
const outputRelativeDirectory = projectConfig.sets ? path.join("sets", execution.set) : "root";
|
|
@@ -30,6 +30,7 @@ import { PageHeader } from "../components/layout/PageHeader";
|
|
|
30
30
|
import { Tabs } from "../components/layout/Tabs";
|
|
31
31
|
import { Badge } from "../components/ui/Badge";
|
|
32
32
|
import { CodeBlock } from "../components/ui/CodeBlock";
|
|
33
|
+
import { EntityKey } from "../components/ui/EntityKey";
|
|
33
34
|
import { LabelValueBadge } from "../components/ui/LabelValueBadge";
|
|
34
35
|
import { EmptyState } from "../components/ui/EmptyState";
|
|
35
36
|
import { Input } from "../components/ui/Input";
|
|
@@ -1195,7 +1196,15 @@ export function EntityDetailPage() {
|
|
|
1195
1196
|
return (
|
|
1196
1197
|
<div>
|
|
1197
1198
|
<PageHeader
|
|
1198
|
-
title={
|
|
1199
|
+
title={
|
|
1200
|
+
<span className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-1">
|
|
1201
|
+
<span className="whitespace-nowrap">{entityLabels[type].singular}:</span>
|
|
1202
|
+
<EntityKey
|
|
1203
|
+
value={detail.key}
|
|
1204
|
+
className="min-w-0 text-[1.45rem] font-extrabold leading-tight"
|
|
1205
|
+
/>
|
|
1206
|
+
</span>
|
|
1207
|
+
}
|
|
1199
1208
|
description={
|
|
1200
1209
|
<div className="flex flex-wrap items-center gap-2">
|
|
1201
1210
|
{entity.archived && <Badge tone="danger">archived</Badge>}
|
|
@@ -1638,7 +1647,7 @@ function MessageTranslationOverridesDetails(props: {
|
|
|
1638
1647
|
to={`${getEntityRoute("message", props.messageKey, props.setKey)}/overrides#${key}`}
|
|
1639
1648
|
className="text-sm font-semibold text-primary hover:underline"
|
|
1640
1649
|
>
|
|
1641
|
-
{key}
|
|
1650
|
+
<EntityKey value={key} className="font-semibold" />
|
|
1642
1651
|
</Link>
|
|
1643
1652
|
{row.source === "inherited" && row.from && (
|
|
1644
1653
|
<LabelValueBadge
|
|
@@ -1800,8 +1809,8 @@ export function MessageOverridesTab() {
|
|
|
1800
1809
|
<section key={override.key} className="space-y-4">
|
|
1801
1810
|
<div className="space-y-3">
|
|
1802
1811
|
<div className="group flex items-center gap-2">
|
|
1803
|
-
<h2 id={override.key} className="font-semibold">
|
|
1804
|
-
{override.key}
|
|
1812
|
+
<h2 id={override.key} className="font-semibold [overflow-wrap:anywhere]">
|
|
1813
|
+
<EntityKey value={override.key} className="font-semibold" />
|
|
1805
1814
|
</h2>
|
|
1806
1815
|
<ExamplePermalink targetId={override.key} />
|
|
1807
1816
|
</div>
|
|
@@ -3141,7 +3150,7 @@ export function LocaleDuplicatesTab() {
|
|
|
3141
3150
|
<td className="min-w-0 border-b border-border px-3 py-2">
|
|
3142
3151
|
<Link
|
|
3143
3152
|
to={getEntityRoute("message", messageKey, setKey)}
|
|
3144
|
-
className="font-medium text-primary hover:underline"
|
|
3153
|
+
className="font-medium text-primary [overflow-wrap:anywhere] hover:underline"
|
|
3145
3154
|
>
|
|
3146
3155
|
<ExamplesSearchHighlight
|
|
3147
3156
|
text={messageKey}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.static{position:static}.absolute{position:absolute}.relative{position:relative}.right-0{right:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-1\/2{top:50%}.top-full{top:100%}.z-20{z-index:20}.m-8{margin:2rem}.mx-6{margin-left:1.5rem;margin-right:1.5rem}.mx-auto{margin-left:auto;margin-right:auto}.-ml-px{margin-left:-1px}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-6{margin-bottom:1.5rem}.ml-3{margin-left:.75rem}.ml-auto{margin-left:auto}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-px{margin-top:1px}.box-border{box-sizing:border-box}.\!block{display:block!important}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-\[46px\]{height:46px}.h-auto{height:auto}.h-full{height:100%}.max-h-4{max-height:1rem}.min-h-0{min-height:0px}.min-h-screen{min-height:100vh}.w-1\/2{width:50%}.w-1\/3{width:33.333333%}.w-16{width:4rem}.w-2\/5{width:40%}.w-28{width:7rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-8{width:2rem}.w-\[10\%\]{width:10%}.w-\[12\%\]{width:12%}.w-\[14\%\]{width:14%}.w-\[16\%\]{width:16%}.w-\[18\%\]{width:18%}.w-\[20\%\]{width:20%}.w-\[22\%\]{width:22%}.w-\[28\%\]{width:28%}.w-\[30\%\]{width:30%}.w-\[32\%\]{width:32%}.w-\[34\%\]{width:34%}.w-\[36\%\]{width:36%}.w-\[38\%\]{width:38%}.w-\[40\%\]{width:40%}.w-\[42\%\]{width:42%}.w-\[70\%\]{width:70%}.w-auto{width:auto}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-28{min-width:7rem}.min-w-48{min-width:12rem}.min-w-\[40rem\]{min-width:40rem}.max-w-44{max-width:11rem}.max-w-5xl{max-width:64rem}.max-w-full{max-width:100%}.max-w-none{max-width:none}.flex-1{flex:1 1 0%}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.basis-\[min\(100\%\,22rem\)\]{flex-basis:min(100%,22rem)}.table-fixed{table-layout:fixed}.border-collapse{border-collapse:collapse}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-rows-\[0fr\]{grid-template-rows:0fr}.grid-rows-\[1fr\]{grid-template-rows:1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0{gap:0px}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-1\.5{row-gap:.375rem}.gap-y-3{row-gap:.75rem}.gap-y-8{row-gap:2rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-border>:not([hidden])~:not([hidden]){border-color:var(--mv-color-border)}.self-start{align-self:flex-start}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:8px}.rounded-\[3px\]{border-radius:3px}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.rounded-l{border-top-left-radius:8px;border-bottom-left-radius:8px}.rounded-r{border-top-right-radius:8px;border-bottom-right-radius:8px}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-border{border-color:var(--mv-color-border)}.border-danger-outline{border-color:var(--mv-color-danger-outline)}.border-pill{border-color:var(--mv-color-pill)}.border-primary{border-color:var(--mv-color-primary)}.border-success-outline{border-color:var(--mv-color-success-outline)}.border-transparent{border-color:transparent}.border-warning-outline{border-color:var(--mv-color-warning-outline)}.bg-amber-100{--tw-bg-opacity: 1;background-color:rgb(254 243 199 / var(--tw-bg-opacity, 1))}.bg-background{background-color:var(--mv-color-background)}.bg-danger-surface{background-color:var(--mv-color-danger-surface)}.bg-elevated{background-color:var(--mv-color-elevated)}.bg-header{background-color:var(--mv-color-header)}.bg-header-active{background-color:var(--mv-color-header-active)}.bg-pill{background-color:var(--mv-color-pill)}.bg-success-surface{background-color:var(--mv-color-success-surface)}.bg-surface{background-color:var(--mv-color-surface)}.bg-transparent{background-color:transparent}.bg-warning-surface{background-color:var(--mv-color-warning-surface)}.object-contain{-o-object-fit:contain;object-fit:contain}.object-left{-o-object-position:left;object-position:left}.p-1{padding:.25rem}.p-4{padding:1rem}.p-8{padding:2rem}.p-px{padding:1px}.px-0\.5{padding-left:.125rem;padding-right:.125rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-2{padding-bottom:.5rem}.pb-4{padding-bottom:1rem}.pb-5{padding-bottom:1.25rem}.pb-6{padding-bottom:1.5rem}.pl-1{padding-left:.25rem}.pl-2{padding-left:.5rem}.pl-4{padding-left:1rem}.pl-5{padding-left:1.25rem}.pr-10{padding-right:2.5rem}.pr-2{padding-right:.5rem}.pr-3{padding-right:.75rem}.pr-7{padding-right:1.75rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-8{padding-top:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.align-middle{vertical-align:middle}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-black{font-weight:900}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-4{line-height:1rem}.leading-5{line-height:1.25rem}.leading-snug{line-height:1.375}.tracking-wide{letter-spacing:.025em}.text-danger{color:var(--mv-color-danger)}.text-faint{color:var(--mv-color-faint)}.text-header{color:var(--mv-color-header)}.text-header-text{color:var(--mv-color-header-text)}.text-inherit{color:inherit}.text-muted{color:var(--mv-color-muted)}.text-pill{color:var(--mv-color-pill)}.text-primary{color:var(--mv-color-primary)}.text-text{color:var(--mv-color-text)}.opacity-0{opacity:0}.shadow{--tw-shadow: var(--mv-shadow);--tw-shadow-colored: var(--mv-shadow);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[inset_0_-2px_0_0_rgba\(251\,191\,36\,0\.35\)\]{--tw-shadow: inset 0 -2px 0 0 rgba(251,191,36,.35);--tw-shadow-colored: inset 0 -2px 0 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: var(--mv-shadow-md);--tw-shadow-colored: var(--mv-shadow-md);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: var(--mv-shadow-sm);--tw-shadow-colored: var(--mv-shadow-sm);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-inset{--tw-ring-inset: inset}.ring-amber-400\/25{--tw-ring-color: rgb(251 191 36 / .25)}.ring-black\/5{--tw-ring-color: rgb(0 0 0 / .05)}.ring-offset-2{--tw-ring-offset-width: 2px}.ring-offset-header{--tw-ring-offset-color: var(--mv-color-header)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-\[background-color\,box-shadow\]{transition-property:background-color,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.\[overflow-wrap\:anywhere\]{overflow-wrap:anywhere}:root{color-scheme:light;--mv-color-background: #e5e7eb;--mv-color-surface: #ffffff;--mv-color-elevated: #f8fafc;--mv-color-border: #e5e7eb;--mv-color-text: #374151;--mv-color-muted: #6b7280;--mv-color-primary: #475569;--mv-color-success: #16a34a;--mv-color-warning: #ea580c;--mv-color-danger: #dc2626;--mv-color-header: #1f2937;--mv-color-header-active: #374151;--mv-color-header-text: #f9fafb;--mv-color-pill: #cbd5e1;--mv-color-faint: #9ca3af;--mv-color-placeholder: #9ca3af;--mv-color-success-surface: #bbf7d0;--mv-color-success-outline: #86efac;--mv-color-warning-surface: #fed7aa;--mv-color-warning-outline: #fdba74;--mv-color-danger-surface: #fee2e2;--mv-color-danger-outline: #fca5a5;--mv-color-ring: rgb(71 85 105 / .45);--mv-shadow-sm: 0 1px 2px 0 rgb(15 23 42 / .06);--mv-shadow-md: 0 4px 6px -1px rgb(15 23 42 / .08), 0 2px 4px -2px rgb(15 23 42 / .06);--mv-shadow: 0 1px 3px 0 rgb(15 23 42 / .1), 0 1px 2px -1px rgb(15 23 42 / .08);--mv-shadow-soft: 0 10px 30px rgb(15 23 42 / .08)}body{margin:0;background:var(--mv-color-background);color:var(--mv-color-text);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}a{color:inherit}.placeholder\:text-xs::-moz-placeholder{font-size:.75rem;line-height:1rem}.placeholder\:text-xs::placeholder{font-size:.75rem;line-height:1rem}.placeholder\:leading-snug::-moz-placeholder{line-height:1.375}.placeholder\:leading-snug::placeholder{line-height:1.375}.placeholder\:text-muted::-moz-placeholder{color:var(--mv-color-muted)}.placeholder\:text-muted::placeholder{color:var(--mv-color-muted)}.placeholder\:text-placeholder::-moz-placeholder{color:var(--mv-color-placeholder)}.placeholder\:text-placeholder::placeholder{color:var(--mv-color-placeholder)}.hover\:border-border:hover{border-color:var(--mv-color-border)}.hover\:border-primary:hover{border-color:var(--mv-color-primary)}.hover\:bg-background:hover{background-color:var(--mv-color-background)}.hover\:bg-elevated:hover{background-color:var(--mv-color-elevated)}.hover\:bg-header-active:hover{background-color:var(--mv-color-header-active)}.hover\:text-primary:hover{color:var(--mv-color-primary)}.hover\:text-text:hover{color:var(--mv-color-text)}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-primary:focus{border-color:var(--mv-color-primary)}.focus-visible\:opacity-100:focus-visible{opacity:1}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-header-text:focus-visible{--tw-ring-color: var(--mv-color-header-text)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color: var(--mv-color-ring)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width: 2px}.focus-visible\:ring-offset-background:focus-visible{--tw-ring-offset-color: var(--mv-color-background)}.group:hover .group-hover\:opacity-100{opacity:1}@media(min-width:640px){.sm\:px-4{padding-left:1rem;padding-right:1rem}}@media(min-width:768px){.md\:col-span-2{grid-column:span 2 / span 2}.md\:flex{display:flex}.md\:w-56{width:14rem}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.md\:flex-row{flex-direction:row}.md\:items-start{align-items:flex-start}.md\:items-center{align-items:center}.md\:justify-between{justify-content:space-between}.md\:justify-self-end{justify-self:end}.md\:text-right{text-align:right}}.\[\&_code\]\:rounded-md code{border-radius:.375rem}.\[\&_code\]\:border code{border-width:1px}.\[\&_code\]\:border-faint code{border-color:var(--mv-color-faint)}.\[\&_code\]\:bg-elevated code{background-color:var(--mv-color-elevated)}.\[\&_code\]\:px-1\.5 code{padding-left:.375rem;padding-right:.375rem}.\[\&_code\]\:py-0\.5 code{padding-top:.125rem;padding-bottom:.125rem}.\[\&_code\]\:font-mono code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.\[\&_code\]\:text-\[0\.8125rem\] code{font-size:.8125rem}.\[\&_code\]\:font-medium code{font-weight:500}.\[\&_code\]\:text-text code{color:var(--mv-color-text)}.\[\&_pre\]\:my-3 pre{margin-top:.75rem;margin-bottom:.75rem}.\[\&_pre\]\:rounded-xl pre{border-radius:.75rem}.\[\&_pre\]\:border pre{border-width:1px}.\[\&_pre\]\:border-faint pre{border-color:var(--mv-color-faint)}.\[\&_pre\]\:bg-elevated pre{background-color:var(--mv-color-elevated)}.\[\&_pre\]\:p-4 pre{padding:1rem}.\[\&_pre\]\:shadow-sm pre{--tw-shadow: var(--mv-shadow-sm);--tw-shadow-colored: var(--mv-shadow-sm);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.\[\&_pre_code\]\:m-0 pre code{margin:0}.\[\&_pre_code\]\:block pre code{display:block}.\[\&_pre_code\]\:w-full pre code{width:100%}.\[\&_pre_code\]\:rounded-none pre code{border-radius:0}.\[\&_pre_code\]\:border-0 pre code{border-width:0px}.\[\&_pre_code\]\:bg-transparent pre code{background-color:transparent}.\[\&_pre_code\]\:p-0 pre code{padding:0}.\[\&_pre_code\]\:text-xs pre code{font-size:.75rem;line-height:1rem}.\[\&_pre_code\]\:font-normal pre code{font-weight:400}.\[\&_pre_code\]\:leading-relaxed pre code{line-height:1.625}.\[\&_pre_code\]\:shadow-none pre code{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}
|