Add CompanyWebsite model with label, is_primary flag, and backward
compatibility sync to company.website. Dynamic form in company edit,
separate buttons in contact bar, additional banners in detail view.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OPNsense bridge + CrowdSec plan saved as future roadmap item.
FortiGate hardening changes from 2026-02-16 session documented.
Design doc status updated to roadmap/future.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detailed step-by-step plan covering CrowdSec deployment on NPM/NordaBiz,
OPNsense VM creation and bridge configuration, Suricata IDS/IPS setup,
console enrollment, bridge insertion procedure, and observation period.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Validated design for replacing expired FortiGuard capabilities with
free, open-source security stack: OPNsense VM in transparent bridge
mode with Suricata IPS and CrowdSec distributed agents.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Explicitly delete reply reads, attachments, reports and edit history
before deleting the reply to avoid NotNullViolation on forum_reply_reads.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DataTransfer API may fail on some mobile browsers, leaving fileInput
empty. Added form submit interceptor that uses FormData as fallback
to ensure attachments are sent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Notifications dropdown was clipped on mobile phones due to absolute
positioning and min-width: 320px. Changed to fixed bottom sheet on
screens ≤768px for native-like UX.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Seeds its own test user (no dependency on pre-existing accounts),
logs in, and verifies /dashboard returns 200. Would have caught the
missing current_app import from b76d275.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The onboarding widget added in b76d275 uses current_app.root_path but
current_app was not imported from flask. This caused NameError for all
logged-in users visiting /dashboard.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Visual timeline showing company profile completion status:
- 6 steps computed from existing DB data (no new tables)
- Color-coded badges: member/office/auto responsibility
- Collapsible with localStorage persistence
- Green "complete" state when all steps done
- Action links for incomplete member-owned steps
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DATABASE_URL and PAGESPEED_API_KEY are read at module level (import
time), so load_dotenv must run before third-party imports that
reference these variables.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add load_dotenv() to seo_audit.py so it reads GOOGLE_PAGESPEED_API_KEY
and DATABASE_URL from .env without requiring manual env var passing.
Fixes PageSpeed 429 errors when running audit via SSH.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevent white-space: pre-line from rendering source code newlines
inside <p>, <div> and callout elements in HTML-formatted descriptions.
Plain text descriptions still get line break rendering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ensures newlines in plain text event descriptions render as line
breaks, maintaining compatibility with both HTML and plain text.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add paragraph spacing, larger font, and styled callout box for
pricing/key info in event detail pages. Better visual hierarchy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add image_url column to NordaEvent model with migration 066.
Display event banner image above description in event detail page.
Include converted WebP image for Lean breakfast event (2026-02-20).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
seo_audit.py was missing SSL columns (has_ssl, ssl_expires_at,
ssl_issuer) in its INSERT/UPDATE query, causing all SEO-audited
companies to show has_ssl=false regardless of actual certificate status.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously only validated format (10 digits). Now also validates
the NIP checksum (weights 6,5,7,2,3,4,5,6,7, mod 11) to catch
typos like the one that left a user unlinked from their company.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The scraper was matching facebook.com/tr (Meta Pixel tracking endpoint)
as a valid Facebook profile handle. Added 'tr', 'privacy', 'policies',
'ads', 'business', 'legal', 'flx' to the Facebook exclusion list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Child companies (with parent_company_id) now show parent's NIP and can
fetch data from KRS/CEIDG using inherited NIP. Fixes Alter Energy showing
no NIP despite sharing one with Fiume Studio.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Display up to 3 next events with RSVP status instead of just one
- Add import script for WhatsApp Norda group data (Feb 2026):
events, company updates, Alter Energy, Croatia announcement
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Employees clicking "Edytuj profil" now see a modal with their company's
management team contacts instead of being sent to an edit form they can't use.
Managers and admins continue to access the edit form directly. Direct URL
access to /firma/edytuj is also guarded with redirect + flash message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The analyze_roadmap_with_ai() was using google.generativeai directly
which bypasses API key configuration. Switch to GeminiService which
has the key pre-configured via init_gemini_service().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New analyze_roadmap_with_ai() function sends existing milestones and recent
knowledge facts to Gemini for comprehensive analysis. Returns new milestone
suggestions, status update recommendations, and identified roadmap gaps.
Adds PATCH endpoint for milestone status updates and tabbed UI modal.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The JS was fetching from /api/zopk/milestones (non-existent) instead of
/admin/zopk-api/milestones (actual endpoint). This caused "Unexpected
token '<'" errors as the 404 HTML page was parsed as JSON.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds "Zarządzaj" edit links next to each section header, visible only
to admin users. Links point to the corresponding admin panels (knowledge,
timeline, news, dashboard). Also adds a "Panel ZOPK" button in the hero.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Role field stores uppercase 'ADMIN' (from SystemRole enum). Use
is_admin boolean property which is synced by set_role() for reliable checks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4 new features visible only to admin (role=='admin'):
- Homepage: "Czy wiesz, że?" widget with 3 random high-confidence facts
- /zopk: Knowledge stats, top entities, key numeric facts, fact type distribution
- /zopk: Dated facts timeline (CSS-only vertical timeline)
- /zopk: D3.js entity co-occurrence graph with slider control
No migrations needed - read-only SELECT queries only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Google deprecated text-embedding-004 on v1beta API (returns 404 NOT_FOUND).
Migrated to gemini-embedding-001 with output_dimensionality=768 to maintain
compatibility with 412 existing embeddings in the database.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical bug: CSS selector pipeline stopped at first match even if element
had 0-94 chars of text (empty <article> tags on wnp.pl, polskieradio24.pl,
portalkomunalny.pl, weekendfm.pl). Now skips elements with <200 chars text.
Added domain-specific selectors for: radiogdansk.pl (Elementor),
nadmorski24.pl (Joomla), portalkomunalny.pl, weekendfm.pl, globenergia.pl,
polskieradio24.pl.
Added 9 domains to SKIP_DOMAINS: wnp.pl (paywall), tvp.pl/tvp.info (JS SPA),
gp24.pl/strefaobrony.pl/dziennikbaltycki.pl (Cloudflare), pap.pl,
obserwatorfinansowy.pl, cire.pl (block bots).
Moved 'article' lower in default selectors to avoid matching empty tags first.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Articles with only 100-458 chars were passing validation but contained
metadata/teasers instead of full article text, causing all knowledge
extraction to fail ("Treść za krótka do ekstrakcji"). The 500-char
minimum better aligns with the 200-token chunking requirement (~800 chars).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Long-running Gemini extractions (30-120s per article) caused SSE
connection timeout. Now runs extraction in a thread and sends
heartbeat updates every 10s to keep the connection alive.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Admin was confused by red "Błędy: 2" when scraping/extraction had
expected issues (403, content too short). Changes:
- All scraper/extractor messages translated to Polish
- HTTP 403/404/429 get specific descriptive messages
- Expected failures shown as yellow "Pominięte" instead of red "Błędy"
- "No chunks created" → "Treść za krótka do ekstrakcji"
- Summary label "Błędy" → "Pominięte"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The scrape stream used 'type' field and lacked 'percent', 'message',
'details' - format incompatible with the shared SSE modal handler.
Aligned to match knowledge stream format: status/percent/message/details.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The SSE scrape stream was filtering on ZOPKNews.content_scraped which
doesn't exist in the model. The correct field is scrape_status with
values 'pending', 'failed', 'scraped', 'skipped'.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Flask-Login's current_user proxy loses context inside generator
functions. Same fix as applied to search stream endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds visual guidance through the 4-step ZOPK news pipeline:
- Enhanced stepper with green checkmarks (done), orange pulsing badges
(needs attention), and gray (inactive) states
- Status banners under each step heading with contextual messages
- Transition banners between steps with action buttons
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Brave free tier was returning 429 for ~50% of queries due to back-to-back
requests. Added 1.1s delay between queries and retry with exponential
backoff (1.5s, 3s). Heartbeat endpoint exempted from Flask-Limiter and
interval increased from 30s to 60s to reduce log noise.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create separate SessionLocal() in run_search() thread instead of sharing
main thread's session (SQLAlchemy sessions are not thread-safe). Increase
connection pool_size to 10 with pool_pre_ping for gthread worker support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>