refactor: remove dead code across server, templ pages, and client modules
Deadcode-verified removals (RTA reachability from main, cross-checked with -test and textual xrefs): Go server: - whole files: middleware/transaction.go, middleware/error_handler.go, middleware/password_validator.go (live registration machinery kept), handlers/sync.go, sync/offline.go - unrouted endpoints: auth UpdateUsername/UpdateEmail, timezone settings handler, OPDS RegisterOPDS, Kobo LibrarySync handler, ReaderHandler.ShowReader, JSON-data twins on collections/library - duplicate book-matching handlers (book_matching.go Handler twins, matching.go MatchingHandler twins), superseded helpers (router.parseUUID, handlers.ValidateDeviceToken, normalizePath, userExists, generateLockID) Templ/client: - never-routed pages: api_explorer.templ, queue.templ and their client modules api-explorer.ts, api-explorer-docs.ts, queue.ts - fully orphaned wrapper modules events.ts, dom.ts - broken clearSyncQueue (called a nonexistent endpoint) and the hidden, never-populated sync-queue section on the devices page - 166+ unused exports demoted to private (knip-verified); ~85 truly dead declarations removed via tsc noUnusedLocals fixpoint - stale 'interactive API Explorer' claims dropped from docs The docs drift-gate package (internal/docs), templ-bound modules (dashboard, stats, header, index, login, profile*, bookshelf, collections, toast-error), sqlc-generated code, and web/static are intentionally untouched.
This commit is contained in:
1 parent
8733e83f1f
commit
8631aa00bf
107 files changed
+689
-3443
No files matched your search
@@ -526,14 +526,6 @@ document.querySelectorAll<HTMLElement>("[data-merge-source]").forEach((el) => {
|
||||
);
|
||||
});
|
||||
|
||||
export {
|
||||
checkActiveScans,
|
||||
hideScanProgress,
|
||||
loadWatchStatus,
|
||||
scanAllLibraries,
|
||||
stopScanStatusPolling,
|
||||
};
|
||||
|
||||
// Library admin page helpers (rendering-ownership: these orchestrate htmx
|
||||
// swaps and modal visibility on server-rendered DOM; they render nothing).
|
||||
function toggleLibraryPanel(btn: HTMLElement): void {
|
||||
|
||||
@@ -256,5 +256,4 @@ export {
|
||||
triggerDownload,
|
||||
bannerBookTitle,
|
||||
type AnnotationRow,
|
||||
type AnnotationSource,
|
||||
};
|
||||
@@ -90,18 +90,6 @@ interface ReadingProgress {
|
||||
percentage?: number;
|
||||
last_read_at: string;
|
||||
}
|
||||
async function getReaderMetadata(mediaItemId: string): Promise<ReaderMetadata> {
|
||||
const response = await apiGet(`/media-items/${mediaItemId}`);
|
||||
return handleResponse<ReaderMetadata>(response);
|
||||
}
|
||||
async function updateReadingProgress(
|
||||
mediaItemId: string,
|
||||
progress: ReadingProgress,
|
||||
): Promise<void> {
|
||||
const response = await apiPut(`/media-items/${mediaItemId}/progress`, progress);
|
||||
await handleVoidResponse(response);
|
||||
}
|
||||
|
||||
async function getReadingProgress(mediaItemId: string): Promise<ReadingProgress | null> {
|
||||
const response = await apiGet(`/media-items/${mediaItemId}/progress`);
|
||||
|
||||
@@ -117,14 +105,10 @@ export {
|
||||
apiPost,
|
||||
apiPut,
|
||||
apiDelete,
|
||||
apiPatch,
|
||||
handleResponse,
|
||||
handleVoidResponse,
|
||||
handleError,
|
||||
getReaderMetadata,
|
||||
getReadingProgress, // ADD THIS
|
||||
updateReadingProgress,
|
||||
ReadingProgress,
|
||||
};
|
||||
|
||||
Alpine.data("api", () => ({
|
||||
|
||||
@@ -32,7 +32,7 @@ export type KeyAction =
|
||||
| "media-next"
|
||||
| "media-prev";
|
||||
|
||||
export interface KeySpec {
|
||||
interface KeySpec {
|
||||
// e.key value to match. Single characters are matched
|
||||
// case-insensitively by resolveShortcut; multi-character key names
|
||||
// ("Enter", "ArrowLeft", "MediaPlayPause") must match verbatim.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// (the Android counterpart's persisted sleep setting is exactly the
|
||||
// mistake plan §7 removes).
|
||||
|
||||
export type SleepMode = "off" | "minutes" | "end-of-chapter" | "fading";
|
||||
type SleepMode = "off" | "minutes" | "end-of-chapter" | "fading";
|
||||
|
||||
export interface SleepState {
|
||||
mode: SleepMode;
|
||||
|
||||
+4
-12
@@ -60,12 +60,12 @@ type AudioChapter = { index: number; title: string; start: number; end: number }
|
||||
// layouts) carry several; single-file books carry exactly one spanning
|
||||
// the whole file. start/end are GLOBAL seconds — the same coordinate
|
||||
// system as chapters, bookmarks and audio_offset_seconds.
|
||||
export type AudioTrack = { url: string; start: number; end: number; title?: string };
|
||||
type AudioTrack = { url: string; start: number; end: number; title?: string };
|
||||
|
||||
// trackIndexFor finds the track holding a global position: the last one
|
||||
// whose start is at or before it (zero-duration/unknown-end tracks keep
|
||||
// working because only start matters for membership).
|
||||
export function trackIndexFor(tracks: AudioTrack[], globalSeconds: number): number {
|
||||
function trackIndexFor(tracks: AudioTrack[], globalSeconds: number): number {
|
||||
for (let i = tracks.length - 1; i > 0; i -= 1) {
|
||||
if (globalSeconds >= tracks[i].start) return i;
|
||||
}
|
||||
@@ -73,7 +73,7 @@ export function trackIndexFor(tracks: AudioTrack[], globalSeconds: number): numb
|
||||
}
|
||||
|
||||
// localOffsetFor converts a global position to seconds within track i.
|
||||
export function localOffsetFor(tracks: AudioTrack[], index: number, globalSeconds: number): number {
|
||||
function localOffsetFor(tracks: AudioTrack[], index: number, globalSeconds: number): number {
|
||||
const track = tracks[index];
|
||||
if (!track) return Math.max(0, globalSeconds);
|
||||
return Math.max(0, globalSeconds - track.start);
|
||||
@@ -81,11 +81,7 @@ export function localOffsetFor(tracks: AudioTrack[], index: number, globalSecond
|
||||
|
||||
// globalSecondsFor converts (track i, local seconds) back to the global
|
||||
// position — the inverse of localOffsetFor.
|
||||
export function globalSecondsFor(
|
||||
tracks: AudioTrack[],
|
||||
index: number,
|
||||
localSeconds: number,
|
||||
): number {
|
||||
function globalSecondsFor(tracks: AudioTrack[], index: number, localSeconds: number): number {
|
||||
const track = tracks[index];
|
||||
return (track ? track.start : 0) + Math.max(0, localSeconds);
|
||||
}
|
||||
@@ -1434,8 +1430,4 @@ Alpine.data("audioReader", () => ({
|
||||
this.pushProgress();
|
||||
}, PROGRESS_INTERVAL_MS);
|
||||
},
|
||||
|
||||
fmtTime,
|
||||
}));
|
||||
|
||||
export { fmtTime };
|
||||
@@ -114,15 +114,6 @@ function collectFormData(editorTags: string[]): Record<string, unknown> {
|
||||
return data;
|
||||
}
|
||||
|
||||
export {
|
||||
showReaderPlaceholder,
|
||||
showMetadataEditor,
|
||||
showProgressSyncModal,
|
||||
showNotesModal,
|
||||
hideProgressSyncModal,
|
||||
hideNotesModal,
|
||||
};
|
||||
|
||||
interface MetadataEditorState {
|
||||
openSections: Record<string, boolean>;
|
||||
hasExistingCover: boolean;
|
||||
|
||||
@@ -375,22 +375,6 @@ function logout(): void {
|
||||
// Exports
|
||||
// ============================================================
|
||||
|
||||
export {
|
||||
backToCollection,
|
||||
clearForm,
|
||||
deleteRule,
|
||||
getFieldLabel,
|
||||
getOperatorLabel,
|
||||
handleCreateRule,
|
||||
initCollectionRules,
|
||||
loadRules,
|
||||
logout,
|
||||
renderRules,
|
||||
setupEventDelegation,
|
||||
testRule,
|
||||
toggleRule,
|
||||
};
|
||||
|
||||
Alpine.data("collectionRules", () => ({
|
||||
backToCollection,
|
||||
clearForm,
|
||||
|
||||
@@ -216,22 +216,6 @@ function handleResolveSubmit(event: Event): void {
|
||||
hideResolveModal();
|
||||
}
|
||||
|
||||
export {
|
||||
bulkDismiss,
|
||||
bulkResolve,
|
||||
dismissAllResolved,
|
||||
handleResolveSubmit,
|
||||
hideResolveModal,
|
||||
refreshConflicts,
|
||||
resolveConflict,
|
||||
showResolveModal,
|
||||
filterConflicts,
|
||||
toggleAllConflicts,
|
||||
updateBulkActions,
|
||||
resolveSelected,
|
||||
dismissSelected,
|
||||
};
|
||||
|
||||
Alpine.data("conflicts", () => ({
|
||||
init() {
|
||||
filterConflicts();
|
||||
|
||||
@@ -533,29 +533,6 @@ async function rejectDevice(registrationId: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function clearSyncQueue(): Promise<void> {
|
||||
if (!confirm("Are you sure you want to clear all sync queue items?")) return;
|
||||
|
||||
const token = getToken();
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/queue/clear", {
|
||||
method: "DELETE",
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showToast("Sync queue cleared", "success");
|
||||
} else {
|
||||
showToast("Failed to clear queue", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to clear queue", error);
|
||||
showToast("Failed to clear queue", "error");
|
||||
}
|
||||
}
|
||||
|
||||
function setupEventDelegation(): void {
|
||||
document.addEventListener("click", (e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
@@ -578,34 +555,8 @@ function setupEventDelegation(): void {
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
approveDevice,
|
||||
clearSyncQueue,
|
||||
copyToClipboard,
|
||||
deleteMapping,
|
||||
editMapping,
|
||||
getDeviceIcon,
|
||||
handleAddDevice,
|
||||
handleRevokeDevice,
|
||||
handleSaveDeviceSettings,
|
||||
handleSaveMapping,
|
||||
hideAddDeviceModal,
|
||||
hideAddMappingModal,
|
||||
hideDeviceSettingsModal,
|
||||
hideShelfMappingsModal,
|
||||
loadCollections,
|
||||
loadShelfMappings,
|
||||
rejectDevice,
|
||||
setupEventDelegation,
|
||||
showAddDeviceModal,
|
||||
showAddMappingModal,
|
||||
showDeviceSettings,
|
||||
showShelfMappings,
|
||||
};
|
||||
|
||||
Alpine.data("devices", () => ({
|
||||
approveDevice,
|
||||
clearSyncQueue,
|
||||
copyToClipboard,
|
||||
deleteMapping,
|
||||
editMapping,
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Alpine } from "./alpine";
|
||||
// ─── operator + field metadata (client mirror of
|
||||
// internal/services/filter_registry.go — labels drive the menus) ────
|
||||
|
||||
export interface OpMeta {
|
||||
interface OpMeta {
|
||||
key: string;
|
||||
label: string;
|
||||
negated: boolean;
|
||||
@@ -34,7 +34,7 @@ const op = (key: string, label: string, negated: boolean, arity: OpMeta["arity"]
|
||||
// label cannot drift apart again (a mis-keyed entry threw every
|
||||
// non-self-named operator out of the allow-list: "doesn't contain" was
|
||||
// being sent literally, and the server rightly refused it).
|
||||
export const OPS: Record<string, OpMeta> = Object.fromEntries(
|
||||
const OPS: Record<string, OpMeta> = Object.fromEntries(
|
||||
[
|
||||
op("contains", "contains", false, "many"),
|
||||
op("not_contains", "doesn't contain", true, "many"),
|
||||
@@ -57,7 +57,7 @@ export const OPS: Record<string, OpMeta> = Object.fromEntries(
|
||||
].map((m) => [m.key, m]),
|
||||
);
|
||||
|
||||
export interface FieldMeta {
|
||||
interface FieldMeta {
|
||||
key: string;
|
||||
label: string;
|
||||
group: string;
|
||||
@@ -86,7 +86,7 @@ const TEXT_OPS = ["contains", "not_contains", "is", "is_not", "empty"];
|
||||
const NUM_OPS = ["between", "at_least", "at_most", "empty"];
|
||||
const DATE_OPS = ["within_days", "between", "empty"];
|
||||
|
||||
export const FIELD_GROUPS: { group: string; fields: FieldMeta[] }[] = [
|
||||
const FIELD_GROUPS: { group: string; fields: FieldMeta[] }[] = [
|
||||
{
|
||||
group: "People",
|
||||
fields: [
|
||||
@@ -335,7 +335,7 @@ export const isRule = (n: FilterRuleState | FilterGroupState): n is FilterRuleSt
|
||||
|
||||
// flatParamNames are the wire params the flat deep-link contract pins
|
||||
// (plus the two flat extras the builder owns).
|
||||
export const FLAT_WIRE_NAMES = [
|
||||
const FLAT_WIRE_NAMES = [
|
||||
...Object.values(FLAT_PARAMS),
|
||||
"explicit_filter",
|
||||
"abridged_filter",
|
||||
@@ -432,7 +432,7 @@ export function pruneIncomplete(
|
||||
|
||||
// ─── row projection (flat rendering of the nested tree) ─────────────
|
||||
|
||||
export interface BuilderRow {
|
||||
interface BuilderRow {
|
||||
key: string;
|
||||
path: number[];
|
||||
type: "rule" | "groupOpen" | "groupClose";
|
||||
@@ -528,7 +528,7 @@ interface Suggestion {
|
||||
display?: string;
|
||||
}
|
||||
|
||||
export function filterBuilderData() {
|
||||
function filterBuilderData() {
|
||||
return {
|
||||
// Rule tree (source of truth; hidden wire inputs are synced after
|
||||
// every mutation).
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { Alpine } from "./alpine";
|
||||
import { ALL_LIBRARIES, getSelectedLibrary, setSelectedLibrary } from "./storage";
|
||||
import { ALL_LIBRARIES, setSelectedLibrary } from "./storage";
|
||||
import { showToast } from "./toast";
|
||||
|
||||
export function getCurrentLibraryId(): string {
|
||||
return getSelectedLibrary();
|
||||
}
|
||||
|
||||
export async function switchWithTransition(
|
||||
containerId: string,
|
||||
fetchFn: () => Promise<void>,
|
||||
|
||||
@@ -186,15 +186,6 @@ function hideMatchModal(): void {
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
autoLinkBooks,
|
||||
getSuggestions,
|
||||
hideMatchModal,
|
||||
linkBook,
|
||||
loadUnlinkedBooks,
|
||||
showSuggestionsModal,
|
||||
};
|
||||
|
||||
Alpine.data("linking", () => ({
|
||||
autoLinkBooks,
|
||||
getSuggestions,
|
||||
|
||||
@@ -4,8 +4,6 @@ import { Alpine } from "./alpine";
|
||||
import "./admin";
|
||||
import "./audio";
|
||||
import "./api";
|
||||
import "./api-explorer";
|
||||
import "./api-explorer-docs";
|
||||
import "./bookPicker";
|
||||
import "./bookshelf";
|
||||
import "./filter_builder";
|
||||
@@ -17,8 +15,6 @@ import "./custom-section-builder";
|
||||
import "./dashboard";
|
||||
import "./device-management";
|
||||
import "./docs";
|
||||
import "./dom";
|
||||
import "./events";
|
||||
import "./header";
|
||||
import "./index";
|
||||
import "./library-switcher";
|
||||
@@ -27,7 +23,6 @@ import "./login";
|
||||
import "./password_validation";
|
||||
import "./profile";
|
||||
import "./profile-modal";
|
||||
import "./queue";
|
||||
import "./register";
|
||||
import "./search";
|
||||
import "./series";
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// are positional. Pages are therefore joined gap-aware, with a char→item
|
||||
// map so each match can be covered by the rects of the items it spans.
|
||||
|
||||
export interface PdfSearchItem {
|
||||
interface PdfSearchItem {
|
||||
/** page-fraction rect of this text item */
|
||||
x: number;
|
||||
y: number;
|
||||
@@ -41,7 +41,7 @@ interface RawItem {
|
||||
}
|
||||
|
||||
/** Join one page's text items into a searchable string + item map. */
|
||||
export function buildPageText(
|
||||
function buildPageText(
|
||||
rawItems: RawItem[],
|
||||
viewportWidth: number,
|
||||
viewportHeight: number,
|
||||
|
||||
@@ -43,27 +43,7 @@ export async function saveSettings(settings: Partial<ReaderSettings>): Promise<v
|
||||
localStorage.setItem(LOCALSTORAGE_KEY, JSON.stringify(updated));
|
||||
}
|
||||
}
|
||||
export async function syncSettings(): Promise<void> {
|
||||
const local = localStorage.getItem(LOCALSTORAGE_KEY);
|
||||
if (!local) return;
|
||||
const settings = JSON.parse(local);
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
try {
|
||||
await fetch("/readers/settings", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to sync settings:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
export function getDefaultSettings(): ReaderSettings {
|
||||
function getDefaultSettings(): ReaderSettings {
|
||||
return {
|
||||
chrome_behavior: "auto-hide",
|
||||
progress_mode: "pages",
|
||||
|
||||
@@ -10,8 +10,6 @@ function initRegisterTheme(): void {
|
||||
}
|
||||
}
|
||||
|
||||
export { initRegisterTheme };
|
||||
|
||||
Alpine.data("register", () => ({
|
||||
init() {
|
||||
initRegisterTheme();
|
||||
|
||||
@@ -288,11 +288,8 @@ Alpine.data("theme", () => ({
|
||||
changeTheme,
|
||||
changeWoodPaneling,
|
||||
initializeTheme,
|
||||
loadTheme,
|
||||
loadUserTheme,
|
||||
loadWoodPaneling,
|
||||
revealCurrentTheme,
|
||||
updateThemeIndicators,
|
||||
updateWoodPanelingIndicators,
|
||||
}));
|
||||
|
||||
@@ -301,11 +298,7 @@ export {
|
||||
changeTheme,
|
||||
changeWoodPaneling,
|
||||
initializeTheme,
|
||||
loadTheme,
|
||||
loadUserTheme,
|
||||
loadWoodPaneling,
|
||||
revealCurrentTheme,
|
||||
updateThemeIndicators,
|
||||
updateWoodPanelingIndicators,
|
||||
};
|
||||
export type { ThemeType };
|
||||
@@ -387,21 +387,6 @@ function setupEventDelegation(): void {
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
bulkAutoLink,
|
||||
bulkGetSuggestions,
|
||||
confirmManualLink,
|
||||
displaySuggestions,
|
||||
hideManualLinkModal,
|
||||
searchBooksForLink,
|
||||
searchMatches,
|
||||
selectBookForLink,
|
||||
setupEventDelegation,
|
||||
showBulkManualLink,
|
||||
showManualLinkModal,
|
||||
toggleAllUnlinked,
|
||||
};
|
||||
|
||||
Alpine.data("unlinkedBooks", () => ({
|
||||
bulkAutoLink,
|
||||
bulkGetSuggestions,
|
||||
|
||||
@@ -120,4 +120,4 @@ function createWebSocket(config: {
|
||||
return ws;
|
||||
}
|
||||
|
||||
export { addListener, createWebSocket, disconnectWebSocket, removeListener };
|
||||
export { addListener, createWebSocket, removeListener };
|
||||
Reference in new issue
Block a user