managarten/apps-archived/memoro/apps/mobile/utils/deepComparison.ts
Till-JS 61d181fbc2 chore: archive inactive projects to apps-archived/
Move inactive projects out of active workspace:
- bauntown (community website)
- maerchenzauber (AI story generation)
- memoro (voice memo app)
- news (news aggregation)
- nutriphi (nutrition tracking)
- reader (reading app)
- uload (URL shortener)
- wisekeep (AI wisdom extraction)

Update CLAUDE.md documentation:
- Add presi to active projects
- Document archived projects section
- Update workspace configuration

Archived apps can be re-activated by moving back to apps/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 07:03:59 +01:00

103 lines
2.8 KiB
TypeScript

/**
* Utility for deep comparison of objects.
* This is used to detect changes in deeply nested objects like metadata.processing.
*/
/**
* Checks if a value is an object (not null, not array, not date)
*/
const isObject = (value: unknown): value is Record<string, unknown> => {
return (
value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)
);
};
/**
* Deep compare two objects to detect changes
* Returns true if there are differences, false if identical
*/
export const hasDeepChanges = (
objA: Record<string, any> | undefined | null,
objB: Record<string, any> | undefined | null,
paths: string[] = []
): boolean => {
// If both are undefined or null, no changes
if (objA === undefined && objB === undefined) return false;
if (objA === null && objB === null) return false;
// If only one is undefined/null, there are changes
if (objA === undefined || objA === null) return true;
if (objB === undefined || objB === null) return true;
// For specific paths, check only those paths
if (paths.length > 0) {
return paths.some((path) => {
const parts = path.split('.');
let valueA = objA;
let valueB = objB;
// Navigate the path
for (const part of parts) {
if (valueA === undefined || valueA === null) return valueB !== undefined && valueB !== null;
if (valueB === undefined || valueB === null) return true;
valueA = valueA[part];
valueB = valueB[part];
}
// Compare the final values
if (isObject(valueA) && isObject(valueB)) {
return hasDeepChanges(valueA, valueB);
}
return valueA !== valueB;
});
}
// If no specific paths, check all keys
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
// If the objects have different numbers of keys, they're different
if (keysA.length !== keysB.length) return true;
// Check if any key exists in one object but not the other
for (const key of keysA) {
if (!(key in objB)) return true;
}
// Compare each key's value
for (const key of keysA) {
const valueA = objA[key];
const valueB = objB[key];
// If both values are objects, recurse
if (isObject(valueA) && isObject(valueB)) {
if (hasDeepChanges(valueA, valueB)) return true;
}
// Otherwise, compare values directly
else if (valueA !== valueB) {
return true;
}
}
return false;
};
/**
* Checks specifically for processing status changes in memo metadata
*/
export const hasProcessingStatusChanged = (
prevMetadata: Record<string, any> | undefined | null,
newMetadata: Record<string, any> | undefined | null
): boolean => {
// Specific paths to check for processing status changes
const processingPaths = [
'processing.transcription.status',
'processing.headline.status',
'processing.headline_and_intro.status',
'transcriptionStatus',
];
return hasDeepChanges(prevMetadata, newMetadata, processingPaths);
};