Index: frontend/src/api/axios.js
===================================================================
--- frontend/src/api/axios.js	(revision 0427f599f15f08425a193b5b366fd15cc0a95f42)
+++ frontend/src/api/axios.js	(revision 72053b5fc3ff291757af1cdd64e8fdfcb9e9b17d)
@@ -1,32 +1,6 @@
 import axios from "axios";
-import { getAuthToken, isTokenExpired, logoutAndRedirect } from "../utils/authSession";
 
-const api = axios.create({
-  baseURL: import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080/api",
+export default axios.create({
+  baseURL: "http://localhost:8080/api",
+  withCredentials: true, // important if using cookies
 });
-
-api.interceptors.request.use((config) => {
-  const token = getAuthToken();
-  if (token) {
-    if (isTokenExpired(token)) {
-      logoutAndRedirect("/dashboard");
-      return Promise.reject(new Error("Authentication session expired"));
-    }
-    config.headers = config.headers ?? {};
-    config.headers.Authorization = `Bearer ${token}`;
-  }
-  return config;
-});
-
-api.interceptors.response.use(
-  (response) => response,
-  (error) => {
-    const status = error?.response?.status;
-    if (status === 401 || status === 403) {
-      logoutAndRedirect("/dashboard");
-    }
-    return Promise.reject(error);
-  },
-);
-
-export default api;
Index: frontend/src/api/dailyCompletion.js
===================================================================
--- frontend/src/api/dailyCompletion.js	(revision 0427f599f15f08425a193b5b366fd15cc0a95f42)
+++ 	(revision )
@@ -1,16 +1,0 @@
-import api from "./axios";
-
-export async function computeDailyCompletion(date /* yyyy-mm-dd or undefined */) {
-  const res = await api.post("/discipline/daily-completions/compute", null, {
-    params: date ? { date } : {},
-  });
-  return res?.data;
-}
-
-export async function getDailyCompletions({ page = 0, size = 14 } = {}) {
-  const res = await api.get("/discipline/daily-completions", {
-    params: { page, size },
-  });
-  return res?.data;
-}
-
Index: frontend/src/api/discipline.js
===================================================================
--- frontend/src/api/discipline.js	(revision 0427f599f15f08425a193b5b366fd15cc0a95f42)
+++ 	(revision )
@@ -1,96 +1,0 @@
-import api from "./axios";
-
-export async function getDisciplineStatus() {
-  const res = await api.get("/discipline/status");
-  return Boolean(res?.data?.tracking);
-}
-
-export async function startDisciplineTracking() {
-  return api.post("/discipline/start", {});
-}
-
-export async function getTasks({ page = 0, size = 50 } = {}) {
-  const res = await api.get("/discipline/tasks", { params: { page, size } });
-  return res?.data;
-}
-
-export async function createTask(payload) {
-  const res = await api.post("/discipline/tasks", payload);
-  return res?.data;
-}
-
-export async function updateTask(taskId, payload) {
-  const res = await api.put(`/discipline/tasks/${taskId}`, payload);
-  return res?.data;
-}
-
-export async function updateTaskFinished(taskId, isFinished) {
-  const res = await api.patch(`/discipline/tasks/${taskId}/finished`, {
-    isFinished,
-  });
-  return res?.data;
-}
-
-export async function deleteTask(taskId) {
-  return api.delete(`/discipline/tasks/${taskId}`);
-}
-
-export async function getCustomTrackingCategories() {
-  const res = await api.get("/discipline/custom-tracking-categories");
-  return res?.data;
-}
-
-export async function createCustomTrackingCategory(payload) {
-  const res = await api.post("/discipline/custom-tracking-categories", payload);
-  return res?.data;
-}
-
-export async function getCustomCategoryTasks(customTrackingId) {
-  const res = await api.get(
-    `/discipline/custom-tracking-categories/${customTrackingId}/tasks`
-  );
-  return res?.data;
-}
-
-export async function createCustomCategoryTask(customTrackingId, payload) {
-  const res = await api.post(
-    `/discipline/custom-tracking-categories/${customTrackingId}/tasks`,
-    payload
-  );
-  return res?.data;
-}
-
-export async function updateCustomCategoryTask(customTrackingId, taskId, payload) {
-  const res = await api.put(
-    `/discipline/custom-tracking-categories/${customTrackingId}/tasks/${taskId}`,
-    payload
-  );
-  return res?.data;
-}
-
-export async function updateCustomCategoryTaskFinished(
-  customTrackingId,
-  taskId,
-  isFinished
-) {
-  const res = await api.patch(
-    `/discipline/custom-tracking-categories/${customTrackingId}/tasks/${taskId}/finished`,
-    { isFinished }
-  );
-  return res?.data;
-}
-
-export async function updateCustomCategoryTaskStatus(customTrackingId, taskId, status) {
-  const res = await api.patch(
-    `/discipline/custom-tracking-categories/${customTrackingId}/tasks/${taskId}/status`,
-    { status }
-  );
-  return res?.data;
-}
-
-export async function deleteCustomCategoryTask(customTrackingId, taskId) {
-  return api.delete(
-    `/discipline/custom-tracking-categories/${customTrackingId}/tasks/${taskId}`
-  );
-}
-
Index: frontend/src/api/finance.js
===================================================================
--- frontend/src/api/finance.js	(revision 0427f599f15f08425a193b5b366fd15cc0a95f42)
+++ 	(revision )
@@ -1,26 +1,0 @@
-import api from "./axios";
-
-export async function getFinanceStatus() {
-  const res = await api.get("/finance/status");
-  return Boolean(res?.data?.tracking);
-}
-
-export async function startFinanceTracking(payload) {
-  return api.post("/finance/start", payload);
-}
-
-export async function getFinanceProfile() {
-  const res = await api.get("/finance/profile");
-  return res?.data;
-}
-
-export async function getIncomes({ page = 0, size = 5 } = {}) {
-  const res = await api.get("/finance/incomes", { params: { page, size } });
-  return res?.data;
-}
-
-export async function createIncome(payload) {
-  const res = await api.post("/finance/incomes", payload);
-  return res?.data;
-}
-
Index: frontend/src/api/twelveData.js
===================================================================
--- frontend/src/api/twelveData.js	(revision 0427f599f15f08425a193b5b366fd15cc0a95f42)
+++ 	(revision )
@@ -1,284 +1,0 @@
-const BASE_URL =
-  import.meta.env.VITE_TWELVE_DATA_BASE_URL ?? "https://api.twelvedata.com";
-
-const API_KEY = import.meta.env.VITE_TWELVE_DATA_API_KEY ?? "";
-
-const CACHE_TTL_MS = 5 * 60_000;
-const MAX_SYMBOLS_PER_REQUEST = 20;
-
-// Cache by Twelve Data symbol (normalized)
-const priceCache = new Map();
-const inFlight = new Map();
-
-function getCachedPrice(twelveSymbol) {
-  const item = priceCache.get(twelveSymbol);
-  if (!item) return null;
-  if (Date.now() - item.ts > CACHE_TTL_MS) return null;
-  return item.price;
-}
-
-function setCachedPrice(twelveSymbol, price) {
-  if (!twelveSymbol || typeof price !== "number" || !Number.isFinite(price)) return;
-  priceCache.set(twelveSymbol, { price, ts: Date.now() });
-}
-
-function parsePriceJson(json, requestedSymbols) {
-  const out = {};
-  if (!json || typeof json !== "object" || Array.isArray(json)) return out;
-
-  // Single symbol response
-  if (json.price !== undefined && requestedSymbols.length === 1) {
-    const n = Number(json.price);
-    if (Number.isFinite(n)) out[requestedSymbols[0]] = n;
-    return out;
-  }
-
-  // Multi symbol response: object keyed by symbol
-  for (const sym of requestedSymbols) {
-    const n = Number(json?.[sym]?.price);
-    if (Number.isFinite(n)) out[sym] = n;
-  }
-
-  return out;
-}
-
-async function fetchBatchPrices(twelveSymbols) {
-  const params = new URLSearchParams();
-  params.set("symbol", twelveSymbols.join(","));
-  params.set("apikey", API_KEY);
-
-  const url = `${BASE_URL}/price?${params.toString()}`;
-  const resp = await fetch(url, { method: "GET" });
-
-  if (!resp.ok) {
-    const text = await resp.text().catch(() => "");
-    throw new Error(`Twelve Data request failed (HTTP ${resp.status}) ${text}`);
-  }
-
-  const json = await resp.json();
-  if (json?.status === "error") {
-    throw new Error(String(json?.message ?? "Twelve Data error"));
-  }
-
-  return parsePriceJson(json, twelveSymbols);
-}
-
-async function fetchSinglePrice(twelveSymbol) {
-  const cached = getCachedPrice(twelveSymbol);
-  if (typeof cached === "number") return cached;
-
-  if (inFlight.has(twelveSymbol)) return inFlight.get(twelveSymbol);
-
-  const p = (async () => {
-    const params = new URLSearchParams();
-    params.set("symbol", twelveSymbol);
-    params.set("apikey", API_KEY);
-    const url = `${BASE_URL}/price?${params.toString()}`;
-    const resp = await fetch(url, { method: "GET" });
-    if (!resp.ok) {
-      const text = await resp.text().catch(() => "");
-      throw new Error(`Twelve Data request failed (HTTP ${resp.status}) ${text}`);
-    }
-    const json = await resp.json();
-    if (json?.status === "error") {
-      throw new Error(String(json?.message ?? "Twelve Data error"));
-    }
-    const n = Number(json?.price);
-    if (!Number.isFinite(n)) throw new Error("Twelve Data returned no price");
-    setCachedPrice(twelveSymbol, n);
-    return n;
-  })();
-
-  inFlight.set(twelveSymbol, p);
-  try {
-    return await p;
-  } finally {
-    inFlight.delete(twelveSymbol);
-  }
-}
-
-async function fetchSingleExchangeRate(pairSymbol) {
-  const cached = getCachedPrice(pairSymbol);
-  if (typeof cached === "number") return cached;
-
-  if (inFlight.has(pairSymbol)) return inFlight.get(pairSymbol);
-
-  const p = (async () => {
-    const params = new URLSearchParams();
-    params.set("symbol", pairSymbol);
-    params.set("apikey", API_KEY);
-    const url = `${BASE_URL}/exchange_rate?${params.toString()}`;
-    const resp = await fetch(url, { method: "GET" });
-    if (!resp.ok) {
-      const text = await resp.text().catch(() => "");
-      throw new Error(`Twelve Data request failed (HTTP ${resp.status}) ${text}`);
-    }
-    const json = await resp.json();
-    if (json?.status === "error") {
-      throw new Error(String(json?.message ?? "Twelve Data error"));
-    }
-
-    const n = Number(json?.rate);
-    if (!Number.isFinite(n)) throw new Error("Twelve Data returned no rate");
-    setCachedPrice(pairSymbol, n);
-    return n;
-  })();
-
-  inFlight.set(pairSymbol, p);
-  try {
-    return await p;
-  } finally {
-    inFlight.delete(pairSymbol);
-  }
-}
-
-export function toTwelveSymbol(symbol) {
-  if (!symbol) return "";
-  const s = String(symbol).trim().toUpperCase();
-  if (!s) return "";
-
-  // Already Twelve format (common for FX/crypto)
-  if (s.includes("/")) return s;
-
-  // Common Yahoo-style crypto tickers
-  const cryptoMap = {
-    "BTC-USD": "BTC/USD",
-    "ETH-USD": "ETH/USD",
-    "SOL-USD": "SOL/USD",
-    "BNB-USD": "BNB/USD",
-    "XRP-USD": "XRP/USD",
-    "ADA-USD": "ADA/USD",
-    "DOGE-USD": "DOGE/USD",
-    "AVAX-USD": "AVAX/USD",
-    "DOT-USD": "DOT/USD",
-    "LINK-USD": "LINK/USD",
-    "MATIC-USD": "MATIC/USD",
-    "LTC-USD": "LTC/USD",
-  };
-
-  if (cryptoMap[s]) return cryptoMap[s];
-
-  // Default: assume it's a stock/ETF symbol (AAPL, SPY, BRK-B, ...)
-  return s;
-}
-
-/**
- * Fetch current prices for one or more symbols.
- * Returns an object keyed by *original input symbols* (uppercased), with numeric prices.
- */
-export async function fetchCurrentPricesBySymbol(symbols) {
-  const input = Array.isArray(symbols) ? symbols : [];
-  const uniqueOriginal = Array.from(
-    new Set(input.map((s) => String(s ?? "").trim()).filter(Boolean)),
-  );
-
-  if (uniqueOriginal.length === 0) return {};
-
-  if (!API_KEY) {
-    throw new Error(
-      "Missing Twelve Data API key (set VITE_TWELVE_DATA_API_KEY in frontend/.env.local)",
-    );
-  }
-
-  // Map original -> twelve
-  const originalToTwelve = new Map();
-  for (const orig of uniqueOriginal) {
-    originalToTwelve.set(orig, toTwelveSymbol(orig));
-  }
-
-  const allTwelveSymbols = Array.from(
-    new Set(Array.from(originalToTwelve.values()).filter(Boolean)),
-  );
-
-  const pairSymbols = allTwelveSymbols.filter((s) => String(s).includes("/"));
-  const priceSymbols = allTwelveSymbols.filter((s) => !String(s).includes("/"));
-
-  // Gather prices with cache + resilient fetch (batch first, fall back to per-symbol)
-  const twelvePrices = {};
-  const missing = [];
-  let hadAnyError = false;
-  let lastErrorMessage = "";
-
-  for (const sym of allTwelveSymbols) {
-    const cached = getCachedPrice(sym);
-    if (typeof cached === "number") {
-      twelvePrices[sym] = cached;
-    } else {
-      missing.push(sym);
-    }
-  }
-
-  // Fetch pair symbols via exchange_rate (generally the most reliable for FX/crypto pairs)
-  for (const sym of pairSymbols) {
-    if (twelvePrices[sym] !== undefined) continue;
-    try {
-      const n = await fetchSingleExchangeRate(sym);
-      if (Number.isFinite(n)) twelvePrices[sym] = n;
-    } catch (e) {
-      hadAnyError = true;
-      lastErrorMessage = e?.message || lastErrorMessage;
-    }
-  }
-
-  // Chunk batch requests to avoid overly long URLs; if a batch fails, we continue
-  // with per-symbol fetches so one invalid symbol doesn't break everything.
-  const missingPrices = missing.filter((s) => priceSymbols.includes(s));
-
-  for (let i = 0; i < missingPrices.length; i += MAX_SYMBOLS_PER_REQUEST) {
-    const chunk = missingPrices.slice(i, i + MAX_SYMBOLS_PER_REQUEST);
-    try {
-      const got = await fetchBatchPrices(chunk);
-      for (const [sym, price] of Object.entries(got)) {
-        const n = Number(price);
-        if (Number.isFinite(n)) {
-          twelvePrices[sym] = n;
-          setCachedPrice(sym, n);
-        }
-      }
-
-      // Some symbols might not come back in a multi-symbol response; fetch them individually.
-      for (const sym of chunk) {
-        if (twelvePrices[sym] !== undefined) continue;
-        try {
-          const n = await fetchSinglePrice(sym);
-          if (Number.isFinite(n)) twelvePrices[sym] = n;
-        } catch (e) {
-          // ignore single-symbol failures
-          hadAnyError = true;
-          lastErrorMessage = e?.message || lastErrorMessage;
-        }
-      }
-    } catch (e) {
-      // Fall back to per-symbol if the whole batch failed.
-      hadAnyError = true;
-      lastErrorMessage = e?.message || lastErrorMessage;
-      await Promise.all(
-        chunk.map(async (sym) => {
-          try {
-            const n = await fetchSinglePrice(sym);
-            if (Number.isFinite(n)) twelvePrices[sym] = n;
-          } catch (e2) {
-            // ignore
-            hadAnyError = true;
-            lastErrorMessage = e2?.message || lastErrorMessage;
-          }
-        }),
-      );
-    }
-  }
-
-  // Convert back to original symbols
-  const out = {};
-  for (const [orig, twelve] of originalToTwelve.entries()) {
-    const p = twelvePrices[twelve];
-    if (typeof p === "number") {
-      out[String(orig).toUpperCase()] = p;
-    }
-  }
-
-  if (Object.keys(out).length === 0 && hadAnyError) {
-    throw new Error(lastErrorMessage || "Could not fetch any current prices");
-  }
-
-  return out;
-}
Index: frontend/src/api/yahooFinance.js
===================================================================
--- frontend/src/api/yahooFinance.js	(revision 0427f599f15f08425a193b5b366fd15cc0a95f42)
+++ 	(revision )
@@ -1,148 +1,0 @@
-import { LOCAL_TICKER_SYMBOLS } from "../data/tickers";
-
-const tickerSearchCache = new Map();
-const TICKER_CACHE_TTL_MS = 15 * 60 * 1000;
-
-const quoteCache = new Map();
-const QUOTE_CACHE_TTL_MS = 5 * 60 * 1000;
-let quoteBackoffUntilMs = 0;
-
-async function tryFetchJson(url) {
-  // Note: We intentionally do NOT set custom headers here.
-  // Custom headers usually trigger a CORS preflight which Yahoo won't allow.
-  const resp = await fetch(url, { method: "GET" });
-  if (!resp.ok) {
-    throw new Error(`Yahoo Finance HTTP ${resp.status}`);
-  }
-  return resp.json();
-}
-
-function toTickerOption(raw) {
-  const symbol = raw?.symbol ? String(raw.symbol) : "";
-  if (!symbol) return null;
-
-  const name = raw?.shortname || raw?.longname || raw?.name || "";
-  const exchange = raw?.exchDisp || raw?.exchange || "";
-
-  return {
-    symbol,
-    name: name ? String(name) : "",
-    exchange: exchange ? String(exchange) : "",
-    quoteType: raw?.quoteType ? String(raw.quoteType) : "",
-  };
-}
-
-/**
- * Direct Yahoo Finance search (yfinance data source).
- * Falls back to backend proxy if the browser blocks CORS.
- */
-export async function searchTickers(query, limit = 20) {
-  const q = String(query ?? "").trim();
-  if (!q) return [];
-  const safeLimit = Math.max(1, Math.min(Number(limit) || 20, 25));
-
-  const cacheKey = `${q.toUpperCase()}:${safeLimit}`;
-  const cached = tickerSearchCache.get(cacheKey);
-  const now = Date.now();
-  if (cached && cached.expiresAt > now) {
-    return cached.value;
-  }
-
-  // Offline/local ticker search: avoids Yahoo 429s and keeps the dropdown reliable.
-  const upperQ = q.toUpperCase();
-  const symbols = Array.isArray(LOCAL_TICKER_SYMBOLS) ? LOCAL_TICKER_SYMBOLS : [];
-  const out = symbols
-    .map((s) => String(s ?? "").trim())
-    .filter(Boolean)
-    .filter((s) => s.toUpperCase().includes(upperQ))
-    .slice(0, safeLimit)
-    .map((symbol) => ({ symbol, name: "", exchange: "" }));
-
-  tickerSearchCache.set(cacheKey, {
-    value: out,
-    expiresAt: now + TICKER_CACHE_TTL_MS,
-  });
-  return out;
-}
-
-/**
- * Direct Yahoo Finance quotes (yfinance data source).
- * Returns a map { SYMBOL: priceNumber }.
- * Falls back to backend proxy if the browser blocks CORS.
- */
-export async function getQuotesBySymbol(symbols) {
-  const list = Array.isArray(symbols) ? symbols : [];
-  const unique = Array.from(
-    new Set(
-      list
-        .map((s) => String(s ?? "").trim())
-        .filter(Boolean)
-        .map((s) => s.toUpperCase()),
-    ),
-  );
-
-  if (unique.length === 0) return {};
-
-  const parseYahoo = (json) => {
-    const results = Array.isArray(json?.quoteResponse?.result)
-      ? json.quoteResponse.result
-      : [];
-    const map = {};
-    for (const r of results) {
-      const sym = r?.symbol ? String(r.symbol).toUpperCase() : "";
-      const price = r?.regularMarketPrice;
-      if (!sym) continue;
-      if (typeof price !== "number") continue;
-      map[sym] = price;
-    }
-    return map;
-  };
-
-  const now = Date.now();
-  const out = {};
-
-  // Serve cache first.
-  const toFetch = [];
-  for (const sym of unique) {
-    const cached = quoteCache.get(sym);
-    if (cached && cached.expiresAt > now && typeof cached.price === "number") {
-      out[sym] = cached.price;
-    } else {
-      toFetch.push(sym);
-    }
-  }
-
-  // Production builds: browser CORS blocks Yahoo, so quotes are unavailable without a proxy.
-  if (!import.meta.env.DEV) {
-    return out;
-  }
-
-  // Backoff after 429s.
-  if (now < quoteBackoffUntilMs) {
-    return out;
-  }
-
-  if (toFetch.length === 0) {
-    return out;
-  }
-
-  const yahooUrl = `/yahoo/v7/finance/quote?symbols=${encodeURIComponent(
-    toFetch.join(","),
-  )}`;
-
-  try {
-    const json = await tryFetchJson(yahooUrl);
-    const fresh = parseYahoo(json);
-    for (const [sym, price] of Object.entries(fresh)) {
-      quoteCache.set(sym, { price, expiresAt: Date.now() + QUOTE_CACHE_TTL_MS });
-      out[sym] = price;
-    }
-    return out;
-  } catch (e) {
-    const msg = String(e?.message ?? "");
-    if (msg.includes("HTTP 429")) {
-      quoteBackoffUntilMs = Date.now() + 60_000;
-    }
-    return out;
-  }
-}
